本人被省选搞无语了,水平较低,若被叉请直接私信 D 我(bushi)。

card

考虑二分答案。

设二分的极差为 l,枚举的最小值为 x,那么所有数字都要在 [x,x+l] 之间。

a,b 混在一起排序,双指针维护区间内出现了多少个 a,多少个 b,多少个 a,b 都出现了。当所有位置的 a,b 在区间中都至少出现了一个,区间才可能合法。

对于一个合法区间,区间内只出现了 b 的那些需要翻,这等价于区间外有多少 a,前缀和后缀和维护即可。

时间复杂度 O(nlogn)

#include<bits/stdc++.h>
using namespace std;

#define FILE
//#define int long long
#define LL long long
#define ull unsigned long long
#define pb push_back
#define pii pair<int, int>
#define fst first
#define scd second
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define abs(x) ((x) < 0 ? -(x) : (x))

const int MAXN = 2e6 + 10;
const int INF = 2e9;
//const int mod = 998244353;
//const int mod = 1e9 + 7;

int n, m;
int a[MAXN], b[MAXN];
int pre[MAXN], suf[MAXN];
int buk[MAXN];

struct NODE {
    int x, opt;
    NODE(int _x = 0, int _o = 0) : x(_x), opt(_o) {}
    bool operator < (const NODE &b) const {return x < b.x;}
} s[MAXN];

template<typename T> inline void read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while('0' <= c && c <= '9') {a = (a * 10) + (c ^ 48); c = getchar();}
    a *= f;
}

bool chk(int add) {
    int p = 1, in = 0;
    for(int i = 1; i <= n; ++i) buk[i] = 0;
    for(int i = 1, to; i <= (n * 2); ++i) {
        while(p <= (n * 2) && s[p].x <= s[i].x + add) {
            to = s[p].opt; if(to > n) to -= n;
            if(!(buk[to]++)) in++;
            p++;
        }
        if(in == n && pre[i - 1] + suf[p] <= m) return true;
        to = s[i].opt; if(to > n) to -= n;
        if(!(--buk[to])) in--;
    }        
    return false;
}

signed main() {
#ifdef FILE
    freopen("card.in", "r", stdin);
    freopen("card.out", "w", stdout);
#endif
    read(n); read(m);
    for(int i = 1; i <= n; ++i) read(a[i]), s[i] = NODE(a[i], i);
    for(int i = 1; i <= n; ++i) read(b[i]), s[i + n] = NODE(b[i], i + n);
    sort(s + 1, s + (n * 2) + 1);
    for(int i = 1; i <= (n << 1); ++i) pre[i] = pre[i - 1] + (s[i].opt <= n);
    for(int i = (n << 1); i >= 1; --i) suf[i] = suf[i + 1] + (s[i].opt <= n);
    int L = 0, R = 1e9, mid;
    while(L < R) {
        mid = (L + R) >> 1;
        if(chk(mid)) R = mid;
        else L = mid + 1;
    }
    printf("%d\n", L);
    return 0;
}

matrix

首先考虑如果没有 [0,106] 的限制要怎么做:可以随便构造,比如先令第一行第一列都是 0,然后从左上到右下依次推过来。

考虑如何调整到 [0,106] 之内。如果需要一个位置 (i,j)+1,那么可以让第 i+1,1,+1,1 交替进行,使得原本的方案修改后依旧合法。或者让第 j 列进行这个操作,同样可以使得当前状态仍然合法。

不妨设 pi,qj 分别表示第 i 行、第 j 列进行了多少次这样的操作,然后就会发现对于每一个点 (i,j),若需要他在 [0,106] 之内,那么就形如一个差分约束的形式了。

但是,有时候可能会出现 x+yw 的形式,也就是两个未知数中间是加号,这是差分约束无法解决的。观察发现,这种情况当且仅当当前行、列都恰好是 +1 或者 1 的时候会出现。

那么考虑调整一下 pi,qj 加数的方法,可以这样分配加减:

对于行的操作:

[+  +  + +  + +  +  + +  + +  +  +]

对于列的操作:

[ +  + +  +  + +  + +  +  + +  + ]

这样之后同一个点的 pi,qj 就都是差分的形式了,直接上差分约束就可以了。本题图十分稠密,用 SPFA 还不如 Bellman-Ford 快。

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 610;
const int MAXM = 5e5 + 10;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;

int T, n, m;
int a[MAXN][MAXN], b[MAXN][MAXN];
int dis[MAXN], cnt[MAXN], w[MAXN][MAXN];
bool inq[MAXN];
queue<int> Q;

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

void add(int a, int b, int v) {
    w[a][b] = v, w[b][a] = (int)1e6 - v;
}

bool BellmanFord() {
    memset(cnt, 0, sizeof cnt);
    for(int i = 1; i <= n + m; ++i)
        dis[i] = 0, inq[i] = true;
    for(int t = 1; t <= n + m + 1; ++t) {
        bool flag = false;
        for(int i = 1, l, r; i <= n + m; ++i) {
            if(i <= n) l = n + 1, r = n + m;
            else l = 1, r = n;
            for(int j = l; j <= r; ++j)
                if(dis[j] > dis[i] + w[i][j])
                    dis[j] = dis[i] + w[i][j], flag = true;
        }
        if(!flag) break;
        if(t == n + m + 1) return false;
    }
    return true;
}

void solve() {
    read(n), read(m); memset(w, 0, sizeof w);
    for(int i = 1; i < n; ++i)
        for(int j = 1; j < m; ++j)
            read(b[i][j]);
    for(int i = 1; i <= n; ++i) a[i][1] = 0;
    for(int i = 1; i <= m; ++i) a[1][i] = 0;
    for(int i = 2; i <= n; ++i)
        for(int j = 2; j <= m; ++j)
            a[i][j] = b[i - 1][j - 1] - a[i - 1][j] - a[i - 1][j - 1] - a[i][j - 1];
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            if((i + j) & 1) add(j + n, i, a[i][j]);
            else add(i, j + n, a[i][j]);
        }
    }
    if(!BellmanFord()) return puts("NO"), void();
    puts("YES");
    for(int i = 1; i <= n; ++i) {
        for(int j = 1, opt; j <= m; ++j) {
            opt = ((i + j) & 1) ? -1 : 1;
            printf("%lld ", a[i][j] + (dis[i] - dis[j + n]) * opt);
        }
        putchar('\n');
    }
}

signed main () {
#ifdef FILE
    freopen(".in", "r", stdin);
    freopen(".out", "w", stdout);
#endif
    read(T);
    while(T--) solve();
    return 0;
}

graph

发现若在 f(u,G) 中,uv 有贡献,当且仅当:

  • 存在一条从 uv,且路径上点标号均不小于 v 的路径。
  • 存在一条从 vu,且路径上点标号均不小于 v 的路径。

两个东西对称,讨论一个即可。

先讨论第二个条件。可以枚举 v,然后考虑倒序加边,看要加到什么时候,一对 u,v 才会存在合法的路径。对于一条边 xy,显然只有 y 一侧的联通块中有贡献,那么对于起始点 v,且当前已经存在从 vx 的合法路径,那么就可以更新 y 可以到的位置。可以发现,由于每一对 u,v 都只会记录使其合法的最大编号的边,所以对于一个固定的 v,整张图只会被遍历一次。

第一个条件,只需要把边反过来建即可。最后只需要计算出倒序加入每一条边之后,新增加了多少合法点对,做一个后缀和就行。时间复杂度 O(nm),民间数据卡常。

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
//#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define min(a, b) (a < b ? a : b)
#define max(a, b) (a > b ? a : b)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 1e3 + 10;
const int MAXM = 2e5 + 10;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;

struct EDGE {
    int head[MAXN], siz;
    int nxt[MAXM], to[MAXM];
    inline void add(int x, int y) {
        nxt[++siz] = head[x];
        to[siz] = y, head[x] = siz;
    }
} e[2];

int n, m;
int X[MAXM], Y[MAXM];
int val[2][MAXN][MAXN];
int Ans[MAXM];

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

void DFS(int opt, int x, int st, int v) {
    val[opt][st][x] = v;
    for(int &i = e[opt].head[x], to; i; i = e[opt].nxt[i]) {
        to = e[opt].to[i];
        if(to < st || val[opt][st][to] != m + 2) continue;
        DFS(opt, to, st, v);
    }
}

signed main () {
#ifdef FILE
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
#endif
    read(n), read(m);
    for(int i = 1; i <= m; ++i) read(X[i]), read(Y[i]);
    for(int i = 1; i <= n; ++i) {
        for(int j = i; j <= n; ++j) val[0][i][j] = val[1][i][j] = m + 2;
        val[0][i][i] = val[1][i][i] = m + 1, e[0].siz = e[1].siz = 0;
        for(int j = i; j <= n; ++j) e[0].head[j] = e[1].head[j] = 0;
        for(int j = m; j >= 1; --j) {
            if(X[j] < i || Y[j] < i) continue;
            e[0].add(X[j], Y[j]), e[1].add(Y[j], X[j]);
            if(val[0][i][X[j]] != m + 2 && val[0][i][Y[j]] == m + 2)
                DFS(0, Y[j], i, j);
            if(val[1][i][Y[j]] != m + 2 && val[1][i][X[j]] == m + 2)
                DFS(1, X[j], i, j);
        }
    }
    for(int i = 1; i <= n; ++i)
        for(int j = i; j <= n; ++j)
            if(max(val[0][i][j], val[1][i][j]) < m + 2)
                Ans[min(val[0][i][j], val[1][i][j])]++;
    for(int i = m; i >= 1; --i) Ans[i] += Ans[i + 1];
    for(int i = 1; i <= m + 1; ++i) printf("%d ", Ans[i]);
    return 0;
}

gem

套路地把路径按 lca 拆开,把权值按照 P 中顺序重新编号。

先考虑从 slca 的部分,设 fx 为当前节点向上的最近的,编号为 x 的点。然后就可以倍增了,这里是一个 log

那么现在所有询问都到了 lca,考虑到 t 的路径中可以到哪里。显然这个东西是单调的,所以可以二分。二分一个询问的答案,然后从 t 同样维护类似的倍增数组,检查一下能不能接上就行。

考场脑抽了写了主席树但是懒得重新写了就这样吧

貌似可以用点分治什么的做到一个 log,但这个做法是 O(nlogn+qlog2n)

#include<bits/stdc++.h>
using namespace std;

//#define FILE
//#define int long long
#define LL long long
#define vec vector
#define pii pair<int, int>
#define fst first
#define scd second
#define pb push_back
#define abs(x) ((x) < 0 ? -(x) : (x))

const int MAXN = 2e5 + 10;
const int MAXM = 5e4 + 10;
const int INF = 2e9;
//const int mod = 998244353;
//const int mod = 1e9 + 7;

struct EDGE {
    int head[MAXN], siz;
    int nxt[MAXN << 1], to[MAXN << 1];
    inline void add(int x, int y) {
        nxt[++siz] = head[x];
        to[siz] = y; head[x] = siz;
    }
} e;

struct Query {
    int lca, id;
    Query(int _lca = 0, int _id = 0) : lca(_lca), id(_id) {}
};

int n, m, c, q;
int P[MAXM], w[MAXN], fa[MAXN], dep[MAXN];
int dfn[MAXN], dfnc, top[MAXN], p[MAXN][21];
int nxt[MAXN][21], num[MAXM];
int sum[MAXN * 30], ls[MAXN * 30], rs[MAXN * 30], cnt, root[MAXN];
int ss[MAXN], tt[MAXN], Ans[MAXN];
bool vis[MAXN];
vec<Query> qs[MAXN];

template<typename T> inline void read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while('0' <= c && c <= '9') {a = (a * 10) + (c ^ 48); c = getchar();}
    a *= f;
}

void DFS(int x, int f) {
    fa[x] = f;
    p[x][0] = f, dep[x] = dep[f] + 1;
    for(int i = 1; i < 20; ++i)
        p[x][i] = p[p[x][i - 1]][i - 1];
    for(int i = e.head[x], to; i; i = e.nxt[i]) {
        to = e.to[i];
        if(to == f) continue;
        DFS(to, x);
    }
}

int LCA(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = 19; ~i; --i)
        if(dep[p[x][i]] >= dep[y]) x = p[x][i];
    if(x == y) return x;
    for(int i = 19; ~i; --i)
        if(p[x][i] ^ p[y][i]) x = p[x][i], y = p[y][i];
    return p[x][0];
}

void trans() {
    static int id[MAXM];
    for(int i = 1; i <= c; ++i) id[P[i]] = i, P[i] = i;
    for(int i = 1; i <= n; ++i) w[i] = id[w[i]];
}

void calc1(int x) {
    int sav = num[w[x]];
    if(w[x]) {
        num[w[x]] = x;
        nxt[x][0] = num[w[x] + 1];
        for(int i = 1; i < 20; ++i)
            nxt[x][i] = nxt[nxt[x][i - 1]][i - 1];
    }
    if(qs[x].size()) {
        for(int i = 0; i < qs[x].size(); ++i) {
            int s = num[1];
            if(dep[s] < dep[qs[x][i].lca]) continue;
            for(int j = 19; ~j; --j)
                if(dep[nxt[s][j]] >= dep[qs[x][i].lca]) s = nxt[s][j];
            Ans[qs[x][i].id] = w[s];
        }
        qs[x].clear();
    }
    for(int i = e.head[x], to; i; i = e.nxt[i]) {
        to = e.to[i];
        if(to == fa[x]) continue;
        calc1(to);
    }
    num[w[x]] = sav;
}

void add(int &x, int pre, int l, int r, int p, int v) {
    if(p < l || p > r) return;
    x = ++cnt;
    sum[x] = sum[pre], ls[x] = ls[pre], rs[x] = rs[pre];
    sum[x] += v;
    if(l == r) return;
    int mid = (l + r) >> 1;
    if(p <= mid) add(ls[x], ls[pre], l, mid, p, v);
    else add(rs[x], rs[pre], mid + 1, r, p, v);
}

int kth(int a, int b, int l, int r, int k) {
    if(sum[a] - sum[b] < k) return -1;
    if(l == r) return l;
    int mid = (l + r) >> 1, v = sum[ls[a]] - sum[ls[b]];
    if(k <= v) return kth(ls[a], ls[b], l, mid, k);
    else return kth(rs[a], rs[b], mid + 1, r, k - v);
}

void solve(int ii, int x) {
    static int lca, lim, l, r, mid, st, j;
    lca = qs[x][ii].lca;
    lim = Ans[qs[x][ii].id];
    l = 1, r = sum[root[x]] - sum[root[lca]];
    if(l > r) return;
    while(l < r) {
        mid = (l + r + 1) >> 1;
        st = num[kth(root[x], root[lca], 1, c, mid)];
        for(j = 19; ~j; --j)
            if(dep[nxt[st][j]] >= dep[lca]) st = nxt[st][j];
        if(w[st] <= lim + 1) l = mid;
        else r = mid - 1;
    }
    st = num[kth(root[x], root[lca], 1, c, l)];
    for(j = 19; ~j; --j)
        if(dep[nxt[st][j]] >= dep[lca]) st = nxt[st][j];
    if(w[st] <= lim + 1)
        Ans[qs[x][ii].id] = max(Ans[qs[x][ii].id], kth(root[x], root[lca], 1, c, l));
}

void calc2(int x) {
    if(w[x]) add(root[x], root[fa[x]], 1, c, w[x], 1);
    else root[x] = root[fa[x]];
    int sav = num[w[x]];
    if(w[x]) {
        num[w[x]] = x;
        nxt[x][0] = num[w[x] - 1];
        for(int i = 1; i < 20; ++i)
            nxt[x][i] = nxt[nxt[x][i - 1]][i - 1];
    }
    for(int ii = 0, j; ii < qs[x].size(); ++ii) solve(ii, x);
    for(int i = e.head[x]; i; i = e.nxt[i])
        if(e.to[i] != fa[x]) calc2(e.to[i]);
    num[w[x]] = sav;
}

signed main() {
#ifdef FILE
    freopen("gem.in", "r", stdin);
    freopen("gem.out", "w", stdout);
#endif
    read(n), read(m), read(c);
    for(int i = 1; i <= c; ++i) read(P[i]);
    for(int i = 1; i <= n; ++i) read(w[i]);
    trans();
    for(int i = 1, x, y; i < n; ++i) {
        read(x), read(y);
        e.add(x, y), e.add(y, x);
    }
    DFS(1, 0); read(q);
    for(int i = 1; i <= q; ++i) {
        read(ss[i]), read(tt[i]);
        qs[ss[i]].pb(Query(LCA(ss[i], tt[i]), i));
    }
    calc1(1);
    memset(nxt, 0, sizeof nxt), memset(num, 0, sizeof num);
    for(int i = 1; i <= n; ++i) qs[i].clear();
    for(int i = 1; i <= q; ++i)
        qs[tt[i]].pb(Query(LCA(ss[i], tt[i]), i));
    calc2(1);
    for(int i = 1; i <= q; ++i) printf("%d\n", Ans[i]);
    return 0;
}

ranklist

被题面送退役了的题......就 nm 毁一生。

考虑 O(n!n) 的做法,直接暴力枚举一个顺序,然后贪心地做,尽量少地放 b 即可。

套路地用状压 dp 优化这个东西,一个 naive 的想法是设 dpS,i,x,y 表示已经填了 S 集合中的数,当前榜首为 ibi=x,当前 b 的和为 y 的时候的方案数。这个转移要 O(2nn2m2),可能还没有上面那个暴力快。

考虑优化,观察到 b 是单调不降的,这引导我们进行差分。差分之后,我们实际上就不需要知道上一个数的 b 的实际值,只需要考虑当前的 b 要比上一个 b 大多少即可。那么先考虑省略一维 x,设 dpS,i,y 为上述状态删掉 bi=x 这个限制之后的状态。

那么当前只需要枚举一下前驱状态中,b 的值,然后贪心地加尽量少的 b 即可。但是由于我们仍然需要判断 bm,所以要类似提前计算贡献,把后面那些还没有填上的位置都加上当前的 Δb。这样就可以 O(2nn2m),可以通过。

不知道考场上脑子里是装了什么搞得连题都没看懂,活该退役

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 14;
const int MAXM = 510;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;

int n, m, Ans, maxp;
int a[MAXN], popc[(1 << (MAXN - 1)) + 10], p2[MAXN];
int dp[(1 << (MAXN - 1)) + 10][MAXN][MAXM];

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

signed main () {
#ifdef FILE
    freopen(".in", "r", stdin);
    freopen(".out", "w", stdout);
#endif
    read(n), read(m); p2[0] = 1;
    for(int i = 1; i <= n; ++i) p2[i] = p2[i - 1] << 1;
    for(int i = 1; i <= n; ++i) read(a[i]);
    maxp = max_element(a + 1, a + n + 1) - a;
    for(int i = 1; i < p2[n]; ++i) popc[i] = popc[i >> 1] + (i & 1);
    for(int i = 1; i <= n; ++i) {
        if(n * (a[maxp] - a[i] + (i > maxp)) > m) continue;
        dp[1 << (i - 1)][i][(a[maxp] - a[i] + (i > maxp)) * n] = 1;
    }
    for(int S = 1; S < p2[n]; ++S) {
        for(int y = 1; y <= n; ++y) {
            if((S & p2[y - 1]) == 0) continue;
            for(int x = 1, nxt; x <= n; ++x) {
                if(S & p2[x - 1]) continue;
                nxt = S | p2[x - 1];
                for(int pre = 0, add; pre <= m; ++pre) {
                    add = max(a[y] - a[x] + (x > y), 0ll);
                    if(pre + add * (n - popc[S]) > m) break;
                    dp[nxt][x][pre + add * (n - popc[S])] += dp[S][y][pre];
                }
            }
        }
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 0; j <= m; ++j)
            Ans += dp[p2[n] - 1][i][j];
    printf("%lld\n", Ans);
    return 0;
}

dominator

先考虑建立支配树,那么一个点的受支配集就是支配树上的所有祖先。由于本题范围很小,所以可以考虑 O(n2) 建支配树:

由于一开始 1 可以到所有点,所以可以删除每一个点,求出 1 不再能到的那些点,这些点被删除的点支配。接着记录每一个点被多少点支配了,用类似拓扑排序的方式,将点依次入队,然后把他所支配的点的计数器减一。若他支配的点的计数器被减后为 1,那么当前点就是这个被支配的点的父亲,接着把这个被支配的点入队即可。

分类讨论 考虑一个询问 (s,t),若在支配树上是返祖边或重边,那么显然不会有任何用处。

(s,t) 是前向边,也就是从祖先到后代,那么:找到 s 的一个儿子 x,使得 tx 的子树中。那么受支配集会改变的点是:

t 出发,走原图的边且不经过 x,所能达到的所有在支配树上在 x 子树内的点。

有以下的结论:

t 指向一个点 v,使得 v 不在 x 的子树内,那么 v 的受支配集不改变。

vx 的祖先,其受支配集显然不会改变。若 v 不是 x 的祖先,那么 v 一定是 LCA(x,v) 的儿子,因为如果是 LCA(x,v) 的孙子或更深的子孙,那么 v 的父亲就不一定需要被经过了,这不符合支配树的定义。而 vLCA(x,v) 的儿子的话,其受支配集同样不会改变——因为这条路径同样需要经过 LCA(x,v)

那么,只要我不经过 x,且在 x 的子树内,那么就一定出现了一条路径,使得可以不经过 x。这样其受支配集就减小了。

(s,t) 是横叉边,那么询问等价于 (LCA(s,t),t),这就转化为了前向边的情况。

若我通过 (s,t) 这条边,那么相当于我没有经过 LCA(s,t) 以下,到 fa(t) 的所有点,而是经过了 LCA(s,t)s 这段路径。而经过这段新路径,也不可能使得一个点的受支配集加入这条路径中的任何点,因为显然加边不会增大任何点的支配集。

因此,这条路径的贡献等价于 (LCA(s,t),t) 的贡献——都是跳过了 t 的一些祖先,因此转化不会有问题。

时间复杂度 O(nq)

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
//#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 3010;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
//const int mod = 998244353;
//const int G = 3;
//const int base = 131;

struct EDGE {
    int head[MAXN], siz;
    int nxt[MAXN << 1], to[MAXN << 1];
    inline void add(int x, int y) {
        nxt[++siz] = head[x];
        to[siz] = y, head[x] = siz;
    }
} e, T;

int n, m, q, Ans;
int fa[MAXN], dep[MAXN], dfn[MAXN], siz[MAXN], dfnc;
int dom[MAXN][MAXN];
bool vis[MAXN];
queue<int> Q;

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

void build() {
    static int cnt[MAXN];
    for(int i = 1; i <= n; ++i)
        dom[1][i] = dom[i][i] = true;
    for(int ban = 2; ban <= n; ++ban) {
        memset(vis, 0, sizeof(bool) * (n + 2));
        Q.push(1);
        while(Q.size()) {
            int x = Q.front(); Q.pop();
            if(x == ban || vis[x]) continue;
            vis[x] = true;
            for(int i = e.head[x], to; i; i = e.nxt[i]) {
                to = e.to[i];
                if(to == ban || vis[to]) continue;
                Q.push(to);
            }
        }
        for(int i = 1; i <= n; ++i) 
            if(!vis[i]) dom[ban][i] = true;
    }
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j)
            if(dom[i][j]) cnt[j]++;
    Q.push(1);
    while(Q.size()) {
        int x = Q.front(); Q.pop();
        for(int i = 1; i <= n; ++i) {
            if(!dom[x][i]) continue;
            if((--cnt[i]) == 1) T.add(x, i), Q.push(i);
        }
    }
}

void DFS(int x, int f) {
    dfn[x] = ++dfnc, fa[x] = f, dep[x] = dep[f] + 1, siz[x] = 1;
    for(int i = T.head[x], to; i; i = T.nxt[i]) {
        to = T.to[i];
        if(to == f) continue;
        DFS(to, x); siz[x] += siz[to];
    }
}

bool isanc(int x, int y) {
    while(y) {
        if(x == y) return true;
        y = fa[y];
    }
    return false;
}

int LCA(int x, int y) {
    while(x ^ y) {
        if(dep[x] < dep[y]) swap(x, y);
        x = fa[x];
    }
    return x;
}

void calc(int x, int ban) {
    if(vis[x] || ban == x) return;
    vis[x] = true;
    if(dfn[ban] <= dfn[x] && dfn[x] <= dfn[ban] + siz[ban] - 1) Ans++;
    for(int i = e.head[x], to; i; i = e.nxt[i]) {
        to = e.to[i];
        calc(to, ban);
    }
}

void solve() {
    int s, t; read(s), read(t);
    if(isanc(t, s)) return puts("0"), void();
    if(!isanc(s, t)) s = LCA(s, t);
    if(fa[t] == s) return puts("0"), void();
    int ban = t; while(fa[ban] ^ s) ban = fa[ban];
    memset(vis, 0, sizeof(bool) * (n + 2));
    Ans = 0; calc(t, ban);
    printf("%d\n", Ans);
}

signed main () {
#ifdef FILE
    freopen(".in", "r", stdin);
    freopen(".out", "w", stdout);
#endif
    read(n), read(m), read(q);
    for(int i = 1, x, y; i <= m; ++i) {
        read(x), read(y);
        e.add(x, y);
    }
    build(); DFS(1, 0);
    while(q--) solve();
    return 0;
}

标签: 图论, 二分, 二分答案, 构造, 状态压缩, two-pointer, 差分约束, 调整法, 倍增, 动态规划优化, 支配树

已有 7 条评论

  1. woshishabi

  2. 香蕉

  3. 我爱上了ywjj

  4. 我是giagiagiagiagiagia

  5. 我是cxy09

  6. ywjj好漂亮我好爱

  7. ywjj好漂亮我好爱

添加新评论