贪心 + 对顶堆,AC 自动机 + 树状数组,Tarjan 缩点 + 拓扑排序

# A. 「APIO2015」巴邻旁之桥 Palembang Bridges

刚开始的时候没啥思路,然后看到数据范围 k=1k = 1k=2k = 2?!

首先把不需要过河的人都排除掉,然后考虑 k=1k = 1 的情况,那么每一组点对都要经过这个桥,所以就是所有点到这个桥的距离之和,显然中位数是最优的,于是把所有点都存下来求个中位数即可。

再来看 k=2k = 2 的情况,我们可以枚举一个断点,强制令左边的点都从左边的桥走,右边的点都从右边的桥走,所以整个对顶堆快速维护中位数即可。

关于对顶堆维护中位数 P1168 中位数

#include <bits/stdc++.h>
#define ll long long
using namespace std;
namespace IO{
    inline ll read(){
        ll x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x;
    }
    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;
const ll N = 3e5 + 10;
const ll INF = 1e18;
ll m, n;
char op1[5], op2[5];
struct node{
    ll x, y;
    inline bool operator < (const node &b) const{
        return x + y < b.x + b.y;
    }
}per[N];
ll p[N];
ll tot, cnt, ans;
priority_queue <ll> q1, q2;
ll sum1, sum2, s[N];
inline void Push(ll x){
    if(q1.empty() || x <= q1.top()) q1.push(x), sum1 += x;
    else q2.push(-x), sum2 += x;
    if(q2.size() + 1 < q1.size()){
        ll now = q1.top();
        q1.pop(), q2.push(-now);
        sum1 -= now, sum2 += now;
    }
    if(q1.size() + 1 < q2.size()){
        ll now = q2.top();
        q2.pop(), q1.push(-now);
        sum1 -= now, sum2 += now;
    }
}
inline void solve1(){
    sort(p + 1, p + 1 + tot);
    for(ll i = 1; i <= tot; ++i) ans += abs(p[i] - p[cnt]);
    write(ans), puts("");
}
inline void solve2(){
    sort(per + 1, per + 1 + cnt);
    for(ll i = 1; i <= cnt; ++i){
        Push(per[i].x), Push(per[i].y);
        s[i] = sum2 - sum1;
    }
    while(!q1.empty()) q1.pop();
    while(!q2.empty()) q2.pop();
    sum1 = sum2 = 0;
    for(ll i = cnt; i > 0; --i){
        s[i] += sum2 - sum1;
        Push(per[i].x), Push(per[i].y);
    }
    ll mins = INF;
    for(ll i = 1; i <= cnt; ++i) mins = min(mins, s[i]);
    write(ans + mins), puts("");
}
signed main(){
    // freopen("A.in", "r", stdin);
    // freopen("A.out", "w", stdout);
    m = read(), n = read();
    for(ll i = 1, x, y; i <= n; ++i){
        scanf("%s%lld%s%lld", op1, &x, op2, &y);
        if(op1[0] == op2[0]) {ans += abs(x - y); continue;}
        per[++cnt] = (node){x, y};
        p[++tot] = x, p[++tot] = y, ans++;
    }
    if(!cnt) return write(ans), puts(""), 0;
    if(m == 1) solve1();
    else solve2();
    return 0;
}

# B. 「NOI2011」阿狸的打字机

虽然之前做过一遍,但是不会了 QwQ

# 40pts 暴力

暴力建出 ACAC 自动机,对于每组询问暴力在自动机上跑。

# 60pts 暴力

  • 观察到有许多重复的询问,所以把询问离线下来,按 yy 排序,开个桶一块询问。

  • 建出 ACAC 自动机后,把 failfail 指针倒过来,此时问题就变成了查询 xx 在从根到 yy 这条链中出现了多少次,所以找出 dfsdfs 序,把根到 yy 这条链全都标记为 1(这个操作可以用树状数组解决),然后 xx 暴力往下跳。

# 100pts

感觉上面这种做法已经很优了,为什么还是过不了呢?

很简单,因为有很多串多次插入树状数组,所以同样把询问离线下来,按 yy 排个序,统一处理即可。

#include <bits/stdc++.h>
using namespace std;
namespace IO{
    inline int read(){
        int x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x =(x << 3) +(x << 1) + ch - '0', ch = getchar();
        return x;
    }
    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;
const int N = 2e5 + 10;
struct node{
    int v, nxt;
} edge[N];
int head[N], cnt;
struct Query{
    int x, y, id, ans;
    inline bool operator < (const Query &b) const{
        return y < b.y;
    }
} q[N << 1];
char s[N];
int n, m;
int trie[N][27], tot;
int fa[N], fail[N], End[N], num[N];
int vis[N][27];
int in[N], out[N], tim;
int c[N], ans[N];
int ql[N], qr[N];
void add(int x, int y){
    edge[++cnt] =(node){y, head[x]};
    head[x] = cnt;
}
void init(){
    scanf("%s", s + 1);
    int now = 0;
    int len = strlen(s + 1);
    for(int i = 1; i <= len; i++){
        if(s[i] >= 'a' && s[i] <= 'z'){
            if(!trie[now][s[i] - 'a']) trie[now][s[i] - 'a'] = ++tot, fa[tot] = now;
            now = trie[now][s[i] - 'a'];
        }
        if(s[i] == 'B') now = fa[now];
        if(s[i] == 'P') End[++n] = now, num[now] = n;
    }
}
void build(){
    queue<int> q;
    for(int i = 0; i < 26; i++)
        if(trie[0][i]) q.push(trie[0][i]);
    while(!q.empty()){
        int now = q.front();
        q.pop();
        for(int i = 0; i < 26; i++){
            if(trie[now][i])
                fail[trie[now][i]] = trie[fail[now]][i], q.push(trie[now][i]);
            else trie[now][i] = trie[fail[now]][i];
        }
    }
}
void dfs(int x){
    in[x] = ++tim;
    for(int i = head[x]; i; i = edge[i].nxt) dfs(edge[i].v);
    out[x] = tim;
}
void update(int x, int y){
    for(; x <= tim; x +=(x &(-x))) c[x] += y;
}
int query(int x){
    int res = 0;
    for(; x > 0; x -=(x &(-x))) res += c[x];
    return res;
}
void solve(int x){
    update(in[x], 1);
    if(num[x])
        for(int i = ql[num[x]]; i <= qr[num[x]]; i++)
            q[i].ans = query(out[End[q[i].x]]) - query(in[End[q[i].x]] - 1);
    for(int i = 0; i < 26; i++)
        if(vis[x][i]) solve(vis[x][i]);
    update(in[x], -1);
}
int main(){
    init();
    for(int i = 0; i <= tot; i++)
        for(int j = 0; j < 26; j++)
            vis[i][j] = trie[i][j];
    build();
    for(int i = 1; i <= tot; i++) add(fail[i], i);
    dfs(0);
    scanf("%d", &m);
    for(int i = 1; i <= m; i++) q[i] = (Query){read(), read(), i, 0};
    sort(q + 1, q + 1 + m);
    for(int i = 1, pos = 1; i <= m; i = pos){
        ql[q[i].y] = i;
        while(q[i].y == q[pos].y) pos++;
        qr[q[i].y] = pos - 1;
    }
    solve(0);
    for(int i = 1; i <= m; i++) ans[q[i].id] = q[i].ans;
    for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
    return 0;
}

# C. 「CEOI2011」Traffic

观察到这么一句话:保证边在交点以外的任何地方不相交。

qwq

(图源自:ppp204 巨佬的题解

然后把中间到不了的点都删掉,剩下的点就是右边一段连续的区间。

因此我们只需要 dfsdfs 一遍跑出它能到达的最高点与最低点,就计算出了答案。

由于会有环,所以 TarjanTarjan 缩点一下。

#include <bits/stdc++.h>
using namespace std;
namespace IO{
    inline int read(){
        int x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x;
    }
    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;
const int N = 3e5 + 10;
const int M = 9e5 + 10;
const int INF = 2e9;
int n, m, A, B;
struct node{
    int x, y, id;
    inline bool operator < (const node &b) const{
        return y > b.y;
    }
}p[N];
vector <int> g[N], G[N];
vector <node> L, R;
int ans[N];
int mx[N], mn[N];
int dfn[N], low[N], tim;
int stk[N], vis[N], top;
int scc[N], cnt;
inline void tarjan(int x){
    dfn[x] = low[x] = ++tim;
    stk[++top] = x, vis[x] = 1;
    if(p[x].x == A) R.push_back(p[x]);
    for(auto y : g[x]){
        if(!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
        else if(vis[y]) low[x] = min(low[x], dfn[y]);
    }
    if(dfn[x] == low[x]){
        int k; cnt++;
        do{
            k = stk[top--];
            vis[k] = 0;
            scc[k] = cnt;
        }while(x != k);
    }
}
inline void dfs(int x){
    if(vis[x]) return;
    vis[x] = 1;
    for(auto y : G[x]){
        dfs(y);
        mx[x] = max(mx[x], mx[y]), mn[x] = min(mn[x], mn[y]);
    }
}
int main(){
    n = read(), m = read(), A = read(), B = read();
    for(int i = 1; i <= n; ++i) p[i] = (node){read(), read(), i};
    for(int i = 1; i <= m; ++i){
        int x = read(), y = read(), k = read();
        g[x].push_back(y);
        if(k == 2) g[y].push_back(x);
    }
    for(int i = 1; i <= n; ++i){
        mx[i] = 0, mn[i] = INF;
        if(!p[i].x){
            L.push_back(p[i]);
            if(!dfn[i]) tarjan(i);
        }
    }
    for(int x = 1; x <= n; ++x)
        for(auto y : g[x])
            if(scc[x] != scc[y]) G[scc[x]].push_back(scc[y]);
    sort(L.begin(), L.end());
    sort(R.begin(), R.end());
    for(auto x : R){
        int now = scc[x.id], res = lower_bound(R.begin(), R.end(), x) - R.begin() + 1;
        mx[now] = max(mx[now], res);
        mn[now] = min(mn[now], res);
    }
    memset(vis, 0, sizeof(vis));
    for(auto u : L){
        dfs(scc[u.id]);
        write(max(mx[scc[u.id]] - mn[scc[u.id]] + 1, 0)), puts("");
    }
    return 0;
}
更新于 阅读次数