【题录】ICPC2020 南京站、济南站

南京站:

J.

操作:区间取max;查询:区间的石堆 + 一堆给定数目 (x) 的石堆所构成的 nim 游戏使先手必胜的先手操作个数(先手第一步的操作方案数)。

由组合游戏的结论有若区间石子的异或和 ^ x 不为 0 则先手必胜,令这个总的异或和为 S。考虑一个石堆,如果先手最开始拿走这个石堆内的石子能够使得异或和 S' 为 0,已知只有一种拿走的操作方式。所以问题只需要考虑一开始动第 k 堆石子能否先手必胜,若能则答案 + 1。除去第 k 堆石子的其余石堆的石子数异或和为 S ^ a[k], 当且仅当 S ^ a[k] < a[k] 的时候 a[k] 可以经过操作变成 S ^ a[k],而当且仅当在 S 的二进制表示中最高位的 1 的位置上,a[k] 也为 1 该条件成立。

因此使用吉司机线段树维护区间取 max 的操作,维护区间异或和以及每一个二进制位上为 1 的区间内数的个数。

#include <bits/stdc++.h>
using namespace std;
#define BITS 32
#define maxn 1000000
#define INF 1500000000
int n, q, a[maxn], minx[maxn], sminx[maxn], minnum[maxn], num[maxn][BITS];
int mark[maxn], sum[maxn], bits[BITS];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void Push_Up(int p, int l, int r) {
    int ls = p << 1, rs = p << 1 | 1;
    if(minx[ls] == minx[rs]) {
        minx[p] = minx[ls], minnum[p] = minnum[ls] + minnum[rs];
        sminx[p] = min(sminx[ls], sminx[rs]);
    }
    else {
        if(minx[rs] < minx[ls]) swap(ls, rs);
        minx[p] = minx[ls], minnum[p] = minnum[ls];
        sminx[p] = min(sminx[ls], minx[rs]);
    }
    for(int K = BITS - 1; K >= 0; K --)
        num[p][K] = num[ls][K] + num[rs][K];
    sum[p] = sum[ls] ^ sum[rs];
}

void Work(int p, int l, int r, int x) {
    if(minx[p] >= x) return;
    for(int K = BITS - 1; K >= 0; K --) {
        if(minx[p] & bits[K]) num[p][K] -= minnum[p];
        if(x & bits[K]) num[p][K] += minnum[p];
    }
    if(minnum[p] & 1) sum[p] ^= minx[p] ^ x;
    minx[p] = x; 
    mark[p] = x;
    return;
}

void Push_Down(int p, int l, int r) {
    if(mark[p] == -INF) return;
    int mid = (l + r) >> 1;
    Work(p << 1, l, mid, mark[p]);
    Work(p << 1 | 1, mid + 1, r, mark[p]);
    mark[p] = -INF; 
}

void Build(int p, int l, int r) {
    mark[p] = -INF;
    if(l == r) {
        minx[p] = sum[p] = a[l]; sminx[p] = INF; minnum[p] = 1;
        for(int K = BITS - 1; K >= 0; K --)
            if(a[l] & bits[K]) num[p][K] ++;
        return;
    }
    int mid = (l + r) >> 1;
    Build(p << 1, l, mid), Build(p << 1 | 1, mid + 1, r);
    Push_Up(p, l, r); 
}

void Modify(int p, int l, int r, int x) {
    if(minx[p] >= x) return;
    if(minx[p] < x && sminx[p] > x) {
        Work(p, l, r, x);
        return;
    }
    int mid = (l + r) >> 1; Push_Down(p, l, r);
    Modify(p << 1, l, mid, x), Modify(p << 1 | 1, mid + 1, r, x);
    Push_Up(p, l, r);
}

void Update(int p, int l, int r, int L, int R, int x) {
    if(l > R || r < L) return;
    if(L <= l && R >= r) {
        Modify(p, l, r, x);
        return;
    }
    int mid = (l + r) >> 1;
    Push_Down(p, l, r);
    Update(p << 1, l, mid, L, R, x);
    Update(p << 1 | 1, mid + 1, r, L, R, x);
    Push_Up(p, l, r);
}

int Query(int p, int l, int r, int L, int R) {
    if(l > R || r < L) return 0;
    if(l >= L && r <= R) return sum[p];
    Push_Down(p, l, r);
    int mid = (l + r) >> 1;
    return Query(p << 1, l, mid, L, R) ^ Query(p << 1 | 1, mid + 1, r, L, R); 
}

int Query2(int p, int l, int r, int L, int R, int x) {
    if(l > R || r < L) return 0;
    if(l >= L && r <= R) return num[p][x];
    Push_Down(p, l, r);
    int mid = (l + r) >> 1;
    return Query2(p << 1, l, mid, L, R, x) + Query2(p << 1 | 1, mid + 1, r, L, R, x);
}

int main() {
    n = read(), q = read(); bits[0] = 1;
    for(int i = 1; i <= n; i ++) a[i] = read();
    for(int i = 1; i < BITS; i ++) bits[i] = bits[i - 1] << 1;
    Build(1, 1, n);
    for(int i = 1; i <= q; i ++) {
        int op = read(), l = read(), r = read(), x = read();
        if(op == 1) Update(1, 1, n, l, r, x);
        else {
            int S = Query(1, 1, n, l, r) ^ x;
            if(!S) printf("0\n");
            else {
                int cnt = -1;
                while(S) S >>= 1, cnt ++;
                printf("%d\n", Query2(1, 1, n, l, r, cnt) + (bool) (x & bits[cnt]));
            }
        }
    }    
    return 0;
}
 

 

M.

简单树形dp, 状态设置 f[i][j][k(0\1)] 代表dp到 i 号节点,子树内已经选择了 j 个节点,当前节点选/不选的情况下干掉整个子树内的怪物所需要的最少能量值。

#include <bits/stdc++.h>
using namespace std;
#define maxn 2005
#define INF 10000000000000
#define LL long long
int n, size[maxn], val[maxn];
LL g[maxn][maxn][2], f[maxn][maxn][2];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

struct edge {
    int cnp = 1, head[maxn], last[maxn], to[maxn];
    void add(int u, int v) {
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
    }
}E;

void Down(LL &x, LL y) {
    if(y < x) x = y;
}

void DP(int u) {
    size[u] = 1;
    f[u][0][0] = val[u];
    f[u][1][1] = 0;
    for(int i = E.head[u]; i; i = E.last[i]) {
        int v = E.to[i];
        DP(v);
        for(int t1 = 0; t1 <= size[v]; t1 ++) 
            for(int t2 = 0; t2 <= size[u]; t2 ++) {
                //down did not choose;
                Down(g[u][t1 + t2][0], f[v][t1][0] + f[u][t2][0] + val[v]);
                Down(g[u][t1 + t2][1], f[v][t1][0] + f[u][t2][1]);
                //down did choose 
                Down(g[u][t1 + t2][0], f[v][t1][1] + f[u][t2][0]);
                Down(g[u][t1 + t2][1], f[v][t1][1] + f[u][t2][1]);
            }
        size[u] += size[v];
        for(int t = 0; t <= size[u]; t ++) {
            f[u][t][0] = g[u][t][0], f[u][t][1] = g[u][t][1];
            g[u][t][0] = g[u][t][1] = INF;
        }
    }
}

int main() {
    int T = read();
    while(T --) {
        n = read(); E.cnp = 1;
        for(int i = 1; i <= n; i ++) E.head[i] = 0;
        for(int i = 2; i <= n; i ++) {
            int x = read();
            E.add(x, i);
        }
        for(int i = 0; i <= n; i ++)
            for(int j = 0; j <= n; j ++)
                for(int t = 0; t <= 1; t ++)
                    f[i][j][t] = g[i][j][t] = INF;
        for(int i = 1; i <= n; i ++) val[i] = read();
        DP(1);
        for(int i = 0; i < n; i ++) printf("%lld ", min(f[1][i][0], f[1][i][1]));
        printf("%lld\n", min(f[1][n][0], f[1][n][1]));
    }
    return 0;
} 

济南站:

A.

列与列之间相互独立,可以拆开来做。每一列都是 n 个 n 元异或方程,求解自由元的个数可以用线性基 + bitset 优化。

H.

令 \(a_{i}\) 为最后一次选择了 \(i\) 路径上的点的时间,\(S\) 为 a_{i} 构成的集合。则此时的次数即为 \(max(S)\)。由 min-max 容斥有 \(max(S) = \sum_{T\subseteq S}^{}(-1)^{|T| - 1}min(T) \),该式对期望也成立,即 \(E(max(S)) = \sum_{T\subseteq S}^{}(-1)^{|T| - 1} E(min(T)) \) 。

而对于给定的路径集合,E(min(T)) 很容易计算。设一共覆盖的有 m 个点,期望的次数即为 n / m 次。

考虑转枚举集合 T 为枚举 m, 使用 dp 计算方案。树形 dp 状态: f[i][j][k][s(0\1)] 代表 dp 到 i 点,子树内覆盖了有 j 个点,已选择的路径向上覆盖了 k 个点,一共选择的路径条数为奇数 /  偶数。我的代码里面把完全覆盖的路径删掉了,但其实可以不用吧……?

#include <bits/stdc++.h>
using namespace std;
#define mod 998244353
#define maxn 350
#define maxm 100000
int n, m, f[maxn][maxn][maxn][2], size[maxn], dep[maxn], len[maxn], R[maxn][maxn];
int recfa[maxn], g[maxn][maxn][2];
bool mark[maxn][maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

struct node {
    int a, b;
}e[maxn];

struct edge {
    int cnp = 1, head[maxm], last[maxm], to[maxm], id[maxm];
    void add(int u, int v) {
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
    }
}E, E2;

int add(int x, int y) {
    x += y; if(x >= mod) x -= mod; 
    return x;
}
int sub(int x, int y) {
    x -= y; if(x < 0) x += mod;
    return x;
}
void Up(int &x, int y) {
    x += y; if(x >= mod) x -= mod;
}
int mul(int x, int y) {
    return 1ll * x * y % mod;
}

int Qpow(int x, int timer) {
    int base = 1;
    for(; timer; timer >>= 1, x = mul(x, x))
        if(timer & 1) base = mul(base, x);
    return base;
}

bool Check(int u, int gra, int id) {
    if(gra != u) {
        for(int i = E2.head[u]; i; i = E2.last[i]) {
            int v = E2.to[i];
            if(dep[v] < dep[e[id].a]) return 0;
        }
    }
    for(int i = E.head[u]; i; i = E.last[i]) {
        int v = E.to[i];
        if(!Check(v, gra, id)) return 0;
    }
    return 1;
}

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1; recfa[u] = fa;
    for(int i = E.head[u]; i; i = E.last[i]) {
        int v = E.to[i];
        dfs(v, u);
    }
}

void DP(int u, int fa) {
    f[u][0][0][0] = 1; 
    for(int i = 1; i <= R[u][0]; i ++) {
        len[u] = max(len[u], dep[u] - dep[R[u][i]] + 1);
        f[u][1][dep[u] - dep[R[u][i]] + 1][1] ++;
    }
    if(len[u] >= 1) size[u] = 1;
    for(int i = E.head[u]; i; i = E.last[i]) {
        int v = E.to[i];
        DP(v, u);
        for(int s1 = 0; s1 <= size[u]; s1 ++)
            for(int l1 = 0; l1 <= len[u]; l1 ++)
                for(int t1 = 0; t1 <= 1; t1 ++)
                    for(int s2 = 0; s2 <= size[v]; s2 ++)
                        for(int l2 = 0; l2 <= len[v]; l2 ++)
                            for(int t2 = 0; t2 <= 1; t2 ++) {
                                int s = s1 + s2;
                                if((!l1) && (l2 >= 2)) s ++;
                                int l = max(l1, l2 - 1);
                                int t = t1 ^ t2;
                                Up(g[s][l][t], mul(f[u][s1][l1][t1], f[v][s2][l2][t2]));
                            }
        if(!len[u] && ((len[v] - 1) >= 1)) size[u] ++;
        size[u] += size[v]; len[u] = max(len[u], len[v] - 1);
        for(int s = 0; s <= size[u]; s ++)
            for(int l = 0; l <= len[u]; l ++)
                for(int t = 0; t <= 1; t ++)
                    f[u][s][l][t] = g[s][l][t], g[s][l][t] = 0;
    }
}

int main() {
    n = read(), m = read();
    for(int i = 2; i <= n; i ++) {
        int x = read();
        recfa[i] = x;
        E.add(x, i);
    }
    dfs(1, 0);
    for(int i = 1; i <= m; i ++) {
        int a = read(), b = read();
        e[i].a = a, e[i].b = b;
        if(mark[a][b]) continue;
        mark[a][b] = 1;
        E2.add(b, a);
    }
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            mark[i][j] = 0;
    for(int i = 1; i <= m; i ++) {
        int a = e[i].a, b = e[i].b;
        if(mark[a][b]) continue;
        mark[a][b] = 1;
        bool flag = true;
        int c = b;
        while(c != recfa[a]) {
            for(int j = E2.head[c]; j; j = E2.last[j]) {
                if(c == b && E2.to[j] == a) continue;
                if(dep[E2.to[j]] >= dep[a]) flag = false;
            }
            c = recfa[c];
        }
        if(flag) R[b][++ R[b][0]] = a;
    }
    DP(1, 0);
    int ans = 0;
    for(int i = 1; i <= n; i ++) {
        int even = add(f[1][i][0][0], f[1][i][1][0]);
        int odd = add(f[1][i][0][1], f[1][i][1][1]);
        Up(ans, mul(Qpow(i, mod - 2), sub(odd, even)));
    }
    ans = mul(ans, n);
    printf("%d\n", ans);
    return 0;
}

J.

官方题解很清晰啦,树是二分图,用两位保证同边的点之间没有连边,不同边的点之间用锁和钥匙的解法。

#include <bits/stdc++.h>
using namespace std;
#define maxn 200000
#define int long long
int n, mark[maxn], a[maxn], rec[maxn], bits[maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}


struct edge {
    int cnp = 1, to[maxn], last[maxn], head[maxn];
    void add(int u, int v) {
        to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++;
        to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++;
    }
}E;

void dfs(int u, int fa) {
    mark[u] = mark[fa] ^ 1;
    for(int i = E.head[u]; i; i = E.last[i]) {
        int v = E.to[i];
        if(v == fa) continue;
        dfs(v, u);
    }
}

signed main() {
    n = read(); bits[0] = 1;
    for(int i = 1; i < 61; i ++) bits[i] = bits[i - 1] << 1;
    for(int i = 1; i < n; i ++) {
        int x = read(), y = read();
        E.add(x, y);
    }
    dfs(1, 0);
    int cnt = 0;
    for(int i = 1; i <= n; i ++)
        if(!mark[i]) rec[i] = ++ cnt;
    if(cnt > n / 2) {
        cnt = 0;
        for(int i = 1; i <= n; i ++) mark[i] ^= 1;
        for(int i = 1; i <= n; i ++) 
            if(!mark[i]) rec[i] = ++ cnt;
            else rec[i] = 0;
    }
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= 60; j ++) a[j] = 0;
        for(int j = cnt + 3; j <= 60; j ++) a[j] = 1;
        if(!mark[i]) {
            for(int j = 1; j <= cnt; j ++) a[j] = (j == rec[i]) ? 0 : 1;
            a[cnt + 1] = 1;
        }
        else {
            for(int j = E.head[i]; j; j = E.last[j]) {
                int v = E.to[j];
                a[rec[v]] = 1;
            }
            a[cnt + 2] = 1;
        }
        int ans = 0;
        for(int j = 1; j <= 60; j ++)
            if(a[j]) ans += bits[j - 1];
        if(i != n) printf("%lld ", ans);
        else printf("%lld\n", ans);
    }
    return 0;
}

K.

考虑把 a 由高位到低位建到一棵 trie 树上面去。那么一个 S 在第 i 位上面为 1 等价于把第 i + 1 位的两棵子树翻转一次。可以用 f[u][K] 表示 u 的子树里面随便翻转后求第 K 小的最小值。这个可以用 dp 去求。(由于树高为 log 的级别,所以最多 n * log 个状态)。如果没有 S 的上下界限制,易知答案即为 f[1][K]。如果有上下界,则从高位到低位所选定的 S 一定经历几个过程:和 L,R 都相同;和 L, R 中的一个相同;由于不卡位后面没有限制。因为只有 log 位,所以可以暴力枚举都相同的位,剩下的在trie 树上面 dp,最后取最小值。

#include <bits/stdc++.h>
using namespace std;
#define maxn 3000005
#define INF 1200000000
#define BITS 31
int T[maxn][2], bits[maxn], tot = 1, size[maxn], n, Q, L, R, K, a[maxn];
int lim;
vector<int> f[maxn];

int read() {
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void Ins(int x) {
    int now = 1;
    for(int i = BITS - 1; i >= 0; i --) {
        int t = (bool) (x & bits[i]);
        if(!T[now][t]) T[now][t] = ++ tot;
        now = T[now][t];
    } 
    size[now] ++;
}

void DP(int u, int dig) {
    if(!u) {
        f[u].resize(1, 0);
        return;
    }
    int ch0 = T[u][0], ch1 = T[u][1];
    if(!ch0 && !ch1) {
        f[u].resize(size[u] + 1, 0);
        return;
    }
    DP(ch0, dig - 1), DP(ch1, dig - 1);
    size[u] += size[ch0], size[u] += size[ch1];
    f[u].resize(size[u] + 1);
    for(int i = 1; i <= size[u]; i ++) {
        // choose to be 0 for the next digit
        if(size[ch0] >= i) f[u][i] = f[ch0][i];
        else f[u][i] = f[ch1][i - size[ch0]] + bits[dig - 1];
        
        //choose to be 1
        if(size[ch1] >= i) f[u][i] = min(f[u][i], f[ch1][i]);
        else f[u][i] = min(f[u][i], f[ch0][i - size[ch1]] + bits[dig - 1]);
    }
}

// lower bound
int Work(int u, int dig, int K, int opt) {
    if(dig == -1) {
        if(K <= size[u]) return 0;
        else return INF;
    }
    int ans = INF;
    bool t = lim & (bits[dig]);
    int opt1 = opt; if(opt < 0) opt1 = -opt;
    if(size[T[u][t]] >= K) ans = min(ans, Work(T[u][t], dig - 1, K, opt1));
    else ans = min(ans, Work(T[u][t ^ 1], dig - 1, K - size[T[u][t]], opt1) + bits[dig]);
    if(((opt == 1) && !t) || ((opt == 2) && t)) {
        t ^= 1;
        if(size[T[u][t]] >= K) ans = min(ans, f[T[u][t]][K]);
        else ans = min(ans, f[T[u][t ^ 1]][K - size[T[u][t]]] + bits[dig]);
    }
    return ans;
}

int main() {
    n = read(), Q = read();    bits[0] = 1;
    for(int i = 1; i <= BITS; i ++) bits[i] = bits[i - 1] << 1;
    for(int i = 1; i <= n; i ++) {
        a[i] = read();
        Ins(a[i]);
    }
    DP(1, BITS);
    for(int i = 1; i <= Q; i ++) {
        L = read(), R = read(), K = read();
        int now = 1, x = 0;
        int ans = INF;
        for(int j = BITS - 1; j >= 0; j --) {
            bool t = L & bits[j];
            if((L & bits[j]) == (R & bits[j])) {
                if(size[T[now][t]] >= K) now = T[now][t];
                else K -= size[T[now][t]], now = T[now][t ^ 1], x += bits[j];
            }
            if((L & bits[j]) != (R & bits[j])) { 
                lim = L, ans = min(ans, x + Work(now, j, K, -1));
                lim = R, ans = min(ans, x + Work(now, j, K, -2));
                break; 
            }
            else if(j == 0) ans = x;
        }
        printf("%d\n", ans);
    }
    return 0;
}

L.

数位dp。因为最多加100,所以在第 7 位至多进位一次。状态 f[x][y][s][t] 代表 dp 到第 x 位,连续的 1  的奇偶性,一共的 1 的奇偶性,是否卡位。最后的 6 位暴力枚举判断是否满足条件。

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define maxn 1000
#define BITS 62
int m, L, ans, a[maxn], cnt[maxn], bits[maxn], f[maxn][2][2][2];

void Up(int &x, int y) {
    x += y;
}

// x : digit number; y : consequent 1s'; s : tot 1s' parity; t : upper limitation;
void trans(int x, int y, int s, int t) {
    int x1 = x - 1; bool num1 = L & bits[x1];
    if(!t || (t && num1)) {
        int y1 = y ^ 1, s1 = s ^ 1, t1 = t;
        Up(f[x1][y1][s1][t1], f[x][y][s][t]);
    }
    int y1 = 0, s1 = s, t1 = t;
    if(t && (num1)) t1 = 0;
    Up(f[x1][y1][s1][t1], f[x][y][s][t]); 
}

void DP() {
    for(int x = BITS - 1; x >= 7; x --)
        for(int y = 0; y <= 1; y ++)
            for(int s = 0; s <= 1; s ++)
                for(int t = 0; t <= 1; t ++)
                    f[x][y][s][t] = 0;
    f[BITS - 1][0][0][1] = 1;
    for(int x = BITS - 1; x >= 8; x --)
        for(int y = 0; y <= 1; y ++)
            for(int s = 0; s <= 1; s ++)
                for(int t = 0; t <= 1; t ++) 
                    if(f[x][y][s][t]) trans(x, y, s, t);
}

void Cal() {
    int lim = L & (bits[7] - 1); ans = 0;
    for(int y = 0; y <= 1; y ++) {
        for(int s = 0; s <= 1; s ++)
            for(int t = 0; t <= 1; t ++) {
                int num1 = f[7][y][s][t];
                if(!num1) continue;
                for(int i = 0; i < bits[7]; i ++) {
                    if(t && (i > lim)) continue;
                    bool flag = true;
                    int num = i; int s1 = s;
                    for(int j = 0; j < m; j ++, num ++) {
                        if(num >= bits[7]) {
                            if(!y) s1 ^= 1; 
                            num = 0;
                        }
                        if((s1 ^ (cnt[num] & 1)) != a[j]) { flag = false; break; }
                    }
                    if(flag) ans += num1;
                }
            }
    }
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; bits[0] = 1;
    for(int i = 1; i < BITS; i ++) bits[i] = bits[i - 1] << 1;
    for(int i = 0; i < bits[7]; i ++) {
        int x = i;
        while(x) { if(x & 1) cnt[i] ++; x >>= 1; }
    }
    while(T --) {
        cin >> m >> L;
        for(int i = 0; i < m; i ++) cin >> a[i];
        DP();
        Cal();
        cout << ans << endl;
    }
    return 0;
}
 

 

posted @ 2021-01-04 11:03  Twilight_Sx  阅读(190)  评论(0编辑  收藏  举报