省选测试8

省选测试8

T1

P4096 [HEOI2013]Eden 的博弈树

​ 树形DP.

​ 对于黑方胜的情况, 如果说当前点为黑方决策, 那么它的所有儿子只需要有一个点是黑方胜的. 那我们需要取答案最小的那一个儿子, 有多个最小的则都取. 如果说当前点是白方决策, 那么它的所有儿子都应该是黑方胜, 所有要取它的儿子的答案的总和. 这样树形DP就好了. 对于白方胜也是一样的.

​ 最后统计答案的时候, 在dfs一遍, 走到当前点答案最小的那个儿子里, 直到当前点为一个叶节点, 那么这个叶节点就是关键点.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 2e5 + 5, inf = 1e9;
int n, cnt;
int p[N], q[N], siz[N], head[N];
vector <int> v;
struct edge { int to, nxt; } e[N << 1];

void add(int x, int y) {
    e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}

void get_tree1(int x, int Fa, int col) {
    siz[x] = 1;
    int Min = inf;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(y == Fa) continue ;
        get_tree1(y, x, 1 - col); siz[x] += siz[y];
        if(col == 0) Min = min(Min, p[y]);
        if(col == 1) p[x] += p[y];
    }
    if(col == 0) p[x] = Min;
    if(siz[x] == 1) p[x] = 1;
}

void get_tree2(int x, int Fa, int col) {
    int Min = inf;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(y == Fa) continue ;
        get_tree2(y, x, 1 - col); 
        if(col == 0) Min = min(Min, q[y]);
        if(col == 1) q[x] += q[y];
    }
    if(col == 0) q[x] = Min;
    if(siz[x] == 1) q[x] = 1;
}

void get_ans(int x, int Fa, int col) {
    if(siz[x] == 1) v.push_back(x);
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(y == Fa) continue ;
        if(col == 0 && p[x] == p[y]) get_ans(y, x, 1 - col);
        if(col == 1 && q[x] == q[y]) get_ans(y, x, 1 - col);
    }
}

int main() {

    // freopen("a.in","r",stdin); freopen("a.out","w",stdout);

    n = read();
    for(int i = 2, x;i <= n; i++)
        x = read(), add(x, i); 
    get_tree1(1, 0, 0);
    get_tree2(1, 0, 1);
    get_ans(1, 0, 0);
    sort(v.begin(), v.end());
    printf("%d %d ", v[0], (int) v.size());
    int res = 0;
    for(int i = 0;i < (int) v.size(); i++) res ^= v[i];
    printf("%d", res);

    fclose(stdin); fclose(stdout);

    return 0;
}

T2

P4098 [HEOI2013]ALO

​ 可持久化Tire树.

​ 首先可持久化Tire是为了找到一个数字在某段区间内的异或最大值的.

​ 那么怎么找比它第二大的值呢.我们可以对所有个位置建成一个链表, 然后把权值从小到大排序, 那么我们先统计到的答案是哪个最小的数字与别的地方的异或最大值. 统计完它之后就把他删除, 删除之后更改链表就可以了.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 5e4 + 5, M = 4e6;
int n, ans, top, tot;
int rt[N], sta[N], pre[N], nxt[N], t[M][2], cnt[M << 1];
struct node { int x, p; } a[N];

int cmp(node a, node b) { return a.x < b.x; }

void insert(int o, int p, int val) {
    for(int i = 31;i >= 0; i--) {
        int x = (val >> i) & 1;
        t[o][0] = t[p][0]; t[o][1] = t[p][1];
        t[o][x] = ++ tot;
        cnt[t[o][x]] = cnt[t[p][x]] + 1;
        o = t[o][x]; p = t[p][x];
        // cout << o << " " << p << "\n";
    }
}

int query(int l, int r, int val) {
    // cout << val << "--------->\n";
    int res = 0;
    for(int i = 31;i >= 0; i--) {
        int x = (val >> i) & 1;
        // cout << x << "\n";
        // cout << l << " " << r << " " << t[l][!x] << " " << t[r][!x] << " " << cnt[t[l][!x]] << " " << cnt[t[r][!x]] << "\n";
        if(cnt[t[r][!x]] - cnt[t[l][!x]] > 0) {
            res |= (1 << i); 
            l = t[l][!x]; r = t[r][!x];
        }
        else {
            l = t[l][x]; r = t[r][x];
        }
    }
    return res;
}

int main() {

    // freopen("b.in","r",stdin); freopen("b.out","w",stdout);

    n = read();
    for(int i = 1;i <= n; i++) {
        a[i].x = read(); a[i].p = i;
        rt[i] = ++ tot; insert(rt[i], rt[i - 1], a[i].x);
        pre[i] = i - 1; nxt[i] = i + 1;
    }
    sort(a + 1, a + n + 1, cmp);
    for(int i = 1, l, r;i <= n; i++) {
        int p = a[i].p;
        l = pre[p]; r = nxt[p];
        pre[r] = l; nxt[l] = r;
        if(l != 0) ans = max(ans, query(rt[pre[l]], rt[r - 1], a[i].x));
        if(r != n + 1) ans = max(ans, query(rt[l], rt[nxt[r] - 1], a[i].x));
    }
    printf("%d", ans);

    // fclose(stdin); fclose(stdout);

    return 0;
}

/*
5
9 2 1 4 7
*/

T3

P4099 [HEOI2013]SAO

​ 还是树形DP.

​ 我们把这个拓扑图看成一颗以1为根的数, 每条表都有一个权值0/1, 代表谁在谁前面/后面.

​ 设\(dp[x][i]\)表示点\(x\)\(x\)的子树内的拓扑序中排名为\(i\)的方案数. 那么我们可以知道 :\(ans = \sum_{i= 1}^n dp[1][i]\).

​ 对于一个节点\(x\)和它的一个儿子\(y\), 现在要将他们合并, \(siz[x]\)代表\(x\)已经合并的子树的大小, \(siz[y]\)表示子树\(v\)的大小.

\(dp[x][k] = dp[x][i] * dp[y][j] * C_{k-1}^{i-1}*C_{siz[x]+siz[y] - k}^{siz[x] - i}\).

​ 什么意思呢? 就是\(x\)新的排名是\(k\), 但是\(x\)原来的排名是\(i\), 对于这\(i\)个数字, 他们合并完之后的相对顺序不能变, 于是我们要在前面\(k-1\)个位置选出\(i-1\)个位置去填原来的那些数, 所以要乘上\(C_{k-1}^{i-1}\), 同理, \(x\)后面的\(siz[x] - i\)个数字的相对顺序也不能变, 后面总共有\(siz[x]+siz[y]-k\)个位置.

​ 这样的话我们需要枚举三个数, 但如果我们最后枚举\(j\), 那么我们可以\(dp[y][j]\)搞成前缀和的形式, 优化一层\(for\)训话, 这样可以做到\(O(n^2)\)的.

​ 要考虑一下\(j\)的范围:

​ 当\(u\)\(v\)前面时 : \(j \in [k - i, siz[v]]\);

​ 当\(u\)\(v\)后面时 : \(j \in [1, min(k-i, siz[v])]\).

inline void dfs(int x, int Fa)
{
    for (int ii = head[x]; ii; ii = e[ii].nxt)
    {
        int v = e[ii].to;
        int tw = e[ii].w;
        if (v == Fa)
            continue;
        dfs(v, x);
        if (!e[ii].w)
        {
            for (int k = siz[x] + siz[v]; k >= 1; k--)
            {
                int sum = 0;
                for (int i = 1; i <= min(k, siz[x]); i++)
                {
                    int l = k - i, r = siz[v], tmp1 = 0, tmp2 = (sumdp[v][r] - sumdp[v][l] + mod) % mod;
                    if (l < r)
                    {
                        tmp1 = 1ll * dp[x][i] * c[k - 1][i - 1] % mod * c[siz[x] + siz[v] - k][siz[x] - i] % mod;
                    }
                    sum = (sum + 1ll * tmp1 * tmp2 % mod) % mod;
                }
                dp[x][k] = sum;
            }
        }
        else
        {
            for (int k = siz[x] + siz[v]; k >= 1; k--)
            {
                int sum = 0;
                for (int i = 1; i <= min(siz[x], k - 1); i++)
                {
                    int r = min(k - i, siz[v]), tmp1 = 0, tmp2 = sumdp[v][r];
                    tmp1 = 1ll * dp[x][i] * c[k - 1][i - 1] % mod * c[siz[x] + siz[v] - k][siz[x] - i] % mod;
                    sum = (sum + 1ll * tmp1 * tmp2 % mod) % mod;
                }
                dp[x][k] = sum;
            }
        }
        siz[x] += siz[v];
    }
    for (int i = 1; i <= siz[x]; i++)
        sumdp[x][i] = (sumdp[x][i - 1] + dp[x][i]) % mod;
}
posted @ 2021-03-09 07:06  C锥  阅读(56)  评论(1编辑  收藏  举报