2020ICPC济南区域赛 补题 & 总结

前言

第一次拿金当然也要记录一下。济南站我们队被分到单独的机房打,愣是用了四台电脑监控四个人(包括志愿者)。

因为以往罚时爆炸的影响,正式赛开始后一直心想着求稳。 zzy \text{zzy} zzy 开局找到 M \text{M} M 题秒出做法后跟我讲,然后我听错直接 hack \text{hack} hack 了,遂把签到题抢了过来自己做。接着 zzy \text{zzy} zzy G \text{G} G 题粗心 WA \text{WA} WA 了两发,顿时心凉了一截,倒是 xbx \text{xbx} xbx 直接过来先把 C \text{C} C 题给秒了,然后我才交了 M \text{M} M 题,紧接着 G G G 题也过了。这时过完前 3 3 3 题还是很快的,但是我们却清楚两发 WA \text{WA} WA 罚时已经落后了。接着跟 xbx \text{xbx} xbx 讨论了下 D \text{D} D 题,过的也不算太慢,但是因为一小时功夫绝大多数队伍都能签到四题,这期间 WA \text{WA} WA 过就很伤。接着由于没榜,我直接扎进数据结构 K \text{K} K 题, zzy \text{zzy} zzy 就去看构造 J \text{J} J 题,未果后跟 xbx \text{xbx} xbx 要了 A \text{A} A 题,认为矩阵相关可能是高斯消元向的题目就喂给我(因为这方面我相对懂的多),我拿过来一看 n 2 n^2 n2 个变元,列完方程再消,即使 bitset \text{bitset} bitset 优化也要 O ( n 5 ) O(n^5) O(n5) 。纸上列了下方程发现独立性后就直接上了,在 1.5h \text{1.5h} 1.5h 直接 1A \text{1A} 1A 过了,这时候已经稳了低罚时了,心情平复了一些。后面两小时我都没有有建设性的想法,期间 zzy \text{zzy} zzy 在构造题疯狂试探,也喂了 xbx \text{xbx} xbx 一道看着是数位 dp \text{dp} dp L \text{L} L 题(因为他相对擅长)。最后我弃了 K \text{K} K 题,帮 xbx \text{xbx} xbx 确认了转移思路后提了几个修补的地方,让他自己调 L \text{L} L 题,又去帮忙 J \text{J} J 题的构造(提了个假做法后, zzy \text{zzy} zzy 改变做法,想到了正解方向,但是没时间了,只能交之前的瞎搞)。几乎放弃的时候, xbx \text{xbx} xbx 一声大吼,在封榜后过了 L \text{L} L 题,之后瞎搞怼 J \text{J} J 题,比赛结束也没过。

结束后刷榜一看, J \text{J} J 题和 L \text{L} L 题各过了 60 60 60 多人,而金牌就 35 35 35 个,只做出其中一题,心想已经没了。切换到排行榜的时候一惊,反复确认后发现还是金了! 6 6 6 题从二十多排到六十多名,看来是大部分队伍也只出了其中一道,甚至有不少队伍是 A \text{A} A 题没出的。最终 rk26 \text{rk26} rk26(去掉前面两个打星队伍),总共三发 WA \text{WA} WA(点名批评某队友,交错题还多 WA \text{WA} WA 一发),但还是靠罚时排到前排了。前期罚时终于没有猛跪,果然面对熟悉的东西才有足够自信在前期直接开掉,几天 cf \text{cf} cf 回炉也有不少作用。虽然其他题目没出也有点遗憾,但是总体来说还是庆幸留了个金尾。今年 EC \text{EC} EC 大概是去不了了,但省赛之类的还是可以继续玩玩的。

题目链接

https://ac.nowcoder.com/acm/contest/10662

参考题解

A - Matrix Equation

简要题意:

给定 n × n n \times n n×n 的矩阵 A , B A, B A,B,求满足 A × C = B ⋅ C A \times C = B \cdot C A×C=BC 的矩阵 C C C 的个数,所有运算都在模 2 2 2 意义下。

1 ≤ n ≤ 200 , A i , j , B i , j ∈ { 0 , 1 } 1 \le n \le 200, A_{i, j}, B_{i, j} \in \{0, 1\} 1n200,Ai,j,Bi,j{0,1}

解题思路:

容易发现 A × C ⋅ , i = B ⋅ , i ⋅ C ⋅ , i A \times C_{\cdot,i} = B_{\cdot, i} \cdot C_{\cdot, i} A×C,i=B,iC,i ,即 C C C 的各列独立,故只要分别求 n n n 列的结果,再根据乘法原理相乘得到最后的答案。每列可以列出 n n n 条异或方程,高斯消元后自由元个数是 x x x 个,满足的列就有 2 x 2^x 2x 个。

参考代码:
#include
using namespace std;
typedef long long ll;
const int maxn = 2e2 + 5;
const int mod = 998244353;

struct Gauss{
    
    int solve(bitset<maxn> A[], int n, int m){

        int r = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r; i < n; ++i){

                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]) { --r; continue; }
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){

                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        return r;
    }
} gs;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    vector<ll> pw2(n + 1);
    pw2[0] = 1;
    for(int i = 1; i <= n; ++i) pw2[i] = pw2[i - 1] * 2 % mod;
    vector<vector<int>> a(n, vector<int>(n)), b = a;
    for(int i = 0; i < n; ++i){

        for(int j = 0; j < n; ++j) cin >> a[i][j];
    }
    for(int i = 0; i < n; ++i){

        for(int j = 0; j < n; ++j) cin >> b[i][j];
    }
    ll ret = 1;
    for(int c = 0; c < n; ++c){

        bitset<maxn> A[maxn];
        for(int i = 0; i < n; ++i){

            A[i].reset();
            for(int j = 0; j < n; ++j){

                A[i][j] = a[i][j];
            }
            A[i][i] = A[i][i] ^ b[i][c];
        }
        int rk = gs.solve(A, n, n);
        if(rk == -1) { ret = 0; break; }
        (ret *= pw2[n - rk]) %= mod;
    }
    cout << ret << endl;
    return 0;
}

B - Number Game

待补。

C - Stone Game

简要题意:

有若干堆石头,其中有 1 , 2 , 3 1, 2, 3 1,2,3 颗石头的分别有 a 1 , a 2 , a 3 a_1, a_2, a_3 a1,a2,a3 堆,每次合并两堆石子,若各有 x , y x, y x,y 个,合并的代价为 (x mod 3)(y mod 3) \text{(x mod 3)(y mod 3)} (x mod 3)(y mod 3) ,求合并所有石子的最小代价。

0 ≤ a i ≤ 1 0 9 , ∑ i = 1 3 a i > 0 0 \le a_i \le 10^9, \sum\limits_{i = 1}^{3} a_i \gt 0 0ai109,i=13ai>0

解题思路:

若合并的其中一堆石子个数为 3 3 3 ,那么代价为 0 0 0 ,故可以忽略。剩下 a 1 a_1 a1 a 2 a_2 a2 ,显然如果同时有 a 1 > 0 a_1 \gt 0 a1>0 a 2 > 0 a_2 \gt 0 a2>0 ,优先对 1 1 1 2 2 2 进行合并,剩下只有一种石子,讨论模 3 3 3 的结果即可。

参考代码:
#include
using namespace std;
typedef long long ll;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    ll a1, a2, a3; cin >> a1 >> a2 >> a3;
    ll tmp = min(a1, a2);
    ll ret = 2 * tmp;
    a1 -= tmp, a2 -= tmp;
    ret += a1 / 3 * 3 + (a1 % 3 == 2 ? 1 : 0);
    ret += a2 / 3 * 6 + (a2 % 3 == 2 ? 4 : 0);
    cout << ret << endl;
    return 0;
}

D - Fight against involution

简要题意:

n n n 个学生,每个学生论文的字数为 w i ∈ [ L i , R i ] w_i \in [L_i, R_i] wi[Li,Ri] ,若有 k i k_i ki 个学生 j ∈ [ 1 , n ] j \in [1, n] j[1,n] 满足 w j > w i w_j \gt w_i wj>wi ,则学生 i i i 的分数为 n − k i n - k_i nki 。最开始所有学生写了 R i R_i Ri 个字,求在每个学生分数不减小的情况下,最小的字数和是多少。

1 ≤ n ≤ 1 0 5 , 1 ≤ L i ≤ R i ≤ 1 0 9 1 \le n \le 10^5, 1 \le L_i \le R_i \le 10^9 1n105,1LiRi109

解题思路:

R i R_i Ri 降序排序后,分数最低的一批学生显然可以同时尽可能减少字数,最小可以减到同分数段里 L i L_i Li 最大的那个。接下来考虑分数第二低的,除了 L i L_i Li 限制,还需要满足新字数仍大于前述那批学生,以此类推。

参考代码:
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    vector<pii> a(n);
    for(int i = 0; i < n; ++i){

        cin >> a[i].second >> a[i].first;
    }
    sort(a.begin(), a.end());
    ll ret = 0; int lst = 0;
    for(int l = 0, r; l < n; l = r){

        r = l;
        while(r < n && a[r].first == a[l].first) ++r;
        int tmp = max(lst, a[r - 1].second);
        ret += (r - l) * 1ll * tmp;
        lst = tmp;
    }
    cout << ret << endl;
    return 0;
}

E - Tree Transform

待补。

F - Gcd Product

待补。

G - Xor Transformation

简要题意:

给定两个正整数 X , Y X, Y X,Y ,每次操作可以选一个数 A A A ,满足 0 ≤ A < X 0 \le A \lt X 0A<X ,令 X = X ⊕ A X = X \oplus A X=XA 。求在五次操作内将 X X X 变成 Y Y Y

1 ≤ Y < X ≤ 1 0 18 1 \le Y \lt X \le 10^{18} 1Y<X1018

解题思路:

讨论 X ⊕ Y X \oplus Y XY 的大小,如果 X ⊕ Y < X X \oplus Y \lt X XY<X ,那么一次操作就可以。否则 X ⊕ Y > X X \oplus Y \gt X XY>X ,先让 X = X ⊕ Y X = X \oplus Y X=XY ,再异或 X X X 即可。

参考代码:
#include
using namespace std;
typedef long long ll;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    ll x, y; cin >> x >> y;
    if((x ^ y) < x){

        cout << "1" << endl;
        cout << (x ^ y) << endl;
    }
    else{

        cout << "2" << endl;
        cout << y << " " << x << endl;
    }
    return 0;
}

H - Path Killer

简要题意:

给定一棵 n n n 个结点的有根树, 1 1 1 号结点为根结点。有 m m m 条路径 ( a i , b i ) (a_i, b_i) (ai,bi) ,其中 a i a_i ai 处在 b i b_i bi 到根的路径上。现在每次随机选择一个结点 u u u 并删除所有经过 u u u 的路径,问删除掉所有路径的期望次数。

1 ≤ n , m ≤ 300 , 1 ≤ a i ≤ b i ≤ n 1 \le n, m \le 300, 1 \le a_i \le b_i \le n 1n,m300,1aibin

解题思路:

w i w_i wi 为第 i i i 条路径被删除所需次数,则答案为 E [ max ⁡ ( w i ) ] = E [ max ⁡ ( S ) ] E[\max(w_i)] = E[\max(S)] E[max(wi)]=E[max(S)] 。最值反演得到答案为 ∑ T ⊆ S ( − 1 ) ∣ T ∣ − 1 E [ min ⁡ ( T ) ] \sum_{T \subseteq S} (-1)^{\vert T\vert - 1} E[\min(T)] TS(1)T1E[min(T)] 。对于给定的路径集合 T T T ,若覆盖了 k k k 个结点, E [ min ⁡ ( T ) ] E[\min(T)] E[min(T)] 就是 n k \cfrac{n}{k} kn d p ( i , j , k , 0 / 1 ) dp(i, j, k, 0/1) dp(i,j,k,0/1) 表示 i i i 子树内覆盖 j j j 个结点、往上覆盖了 k k k 个结点、集合大小为奇数或偶数时候的答案,转移时枚举子树逐一合并。只看前两维就是经典的树上背包复杂度,第三维由于是最值的形式,可以利用前缀和做到 O ( n ) O(n) O(n) ,总时间复杂度就是 O ( n 3 ) O(n^3) O(n3) 。注释代码部分是直接 O ( n 4 ) O(n^4) O(n4) 剪枝跑的,也能过。

参考代码:
#include
using namespace std;
typedef long long ll;
const int maxn = 3e2 + 5;
const int mod = 998244353;

vector<int> G[maxn], a[maxn];
int fa[maxn], siz[maxn], inv[maxn];
int dp[maxn][maxn][maxn][2], ans[maxn];
int n, m;

int getLen(int u, int v){

    int ret = 1;
    while(u != v) u = fa[u], ++ret;
    return ret;
}

void dfs(int u){

    dp[u][0][0][0] = 1, siz[u] = 1;
    for(auto &v : a[u]){

        int l = getLen(u, v);
        memset(dp[0], 0, sizeof dp[0]);
        for(int i = n; i >= 0; --i){

            (dp[0][0][i][0] += dp[u][0][i][0]) %= mod;
            (dp[0][0][i][1] += dp[u][0][i][1]) %= mod;
            (dp[0][0][max(i, l)][0] += dp[u][0][i][1]) %= mod;
            (dp[0][0][max(i, l)][1] += dp[u][0][i][0]) %= mod;
        }
        memcpy(dp[u], dp[0], sizeof dp[0]);
    }
    for(auto &v : G[u]){

        dfs(v);
        memset(dp[0], 0, sizeof dp[0]);
        for(int i = siz[u] - 1; i >= 0; --i){

            for(int j = siz[v] - 1; j >= 0; --j){

                int sum[2] = {};
                for(int k = 0; k <= n; ++k){

                    if(k) (sum[0] += dp[v][j][k][0]) %= mod;
                    if(k) (sum[1] += dp[v][j][k][1]) %= mod;
                    
                    (dp[0][i + j][k][0] += dp[u][i][k][0] * 1ll * dp[v][j][0][0] % mod) %= mod;
                    (dp[0][i + j][k][0] += dp[u][i][k][1] * 1ll * dp[v][j][0][1] % mod) %= mod;
                    (dp[0][i + j][k][1] += dp[u][i][k][0] * 1ll * dp[v][j][0][1] % mod) %= mod;
                    (dp[0][i + j][k][1] += dp[u][i][k][1] * 1ll * dp[v][j][0][0] % mod) %= mod;
                    
                    (dp[0][i + j + 1][k][0] += dp[u][i][k][0] * 1ll * sum[0] % mod) %= mod;
                    (dp[0][i + j + 1][k][0] += dp[u][i][k][1] * 1ll * sum[1] % mod) %= mod;
                    (dp[0][i + j + 1][k][1] += dp[u][i][k][0] * 1ll * sum[1] % mod) %= mod;
                    (dp[0][i + j + 1][k][1] += dp[u][i][k][1] * 1ll * sum[0] % mod) %= mod;
                }
                sum[0] = dp[u][i][0][0], sum[1] = dp[u][i][0][1];
                for(int k = 1; k <= n; ++k){
                    
                    (dp[0][i + j + 1][k - 1][0] += dp[v][j][k][0] * 1ll * sum[0] % mod) %= mod;
                    (dp[0][i + j + 1][k - 1][0] += dp[v][j][k][1] * 1ll * sum[1] % mod) %= mod;
                    (dp[0][i + j + 1][k - 1][1] += dp[v][j][k][0] * 1ll * sum[1] % mod) %= mod;
                    (dp[0][i + j + 1][k - 1][1] += dp[v][j][k][1] * 1ll * sum[0] % mod) %= mod;
                    
                    (sum[0] += dp[u][i][k][0]) %= mod;
                    (sum[1] += dp[u][i][k][1]) %= mod;
                }

                // int p1 = n; while(p1 >= 0 && !dp[u][i][p1][0] && !dp[u][i][p1][1]) --p1;
                // int p2 = n; while(p2 >= 0 && !dp[v][j][p2][0] && !dp[v][j][p2][1]) --p2;
                // for(int k = p1; k >= 0; --k){

                //     for(int l = p2; l >= 0; --l){

                //         (dp[0][i + j + (l ? 1 : 0)][max(k, l - 1)][0] += dp[u][i][k][0] * 1ll * dp[v][j][l][0] % mod) %= mod;
                //         (dp[0][i + j + (l ? 1 : 0)][max(k, l - 1)][0] += dp[u][i][k][1] * 1ll * dp[v][j][l][1] % mod) %= mod;
                //         (dp[0][i + j + (l ? 1 : 0)][max(k, l - 1)][1] += dp[u][i][k][0] * 1ll * dp[v][j][l][1] % mod) %= mod;
                //         (dp[0][i + j + (l ? 1 : 0)][max(k, l - 1)][1] += dp[u][i][k][1] * 1ll * dp[v][j][l][0] % mod) %= mod;
                //     }
                // }
            }
        }
        memcpy(dp[u], dp[0], sizeof dp[0]);
        siz[u] += siz[v];
    }
}

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    inv[0] = inv[1] = 1;
    for(int i = 2; i <= n; ++i){
        
        inv[i] = inv[mod % i] * 1ll * (mod - mod / i) % mod;
    }
    for(int i = 2; i <= n; ++i){

        cin >> fa[i];
        G[fa[i]].emplace_back(i);
    }
    for(int i = 1; i <= m; ++i){

        int x, y; cin >> x >> y;
        a[y].emplace_back(x);
    }
    dfs(1);
    for(int i = 0; i <= n; ++i){

        for(int j = 0; j <= n; ++j){

            if(i + j > n) break;
            (ans[i + j] += dp[1][i][j][1]) %= mod;
            (ans[i + j] -= dp[1][i][j][0]) %= mod;
        }
    }
    ll ret = 0;
    for(int i = 1; i <= n; ++i){

        ll fi = n * 1ll * inv[i] % mod;
        (ret += ans[i] * fi) %= mod;
        // cout << i << " ?? " << ans[i] << endl;
    }
    ret = (ret + mod) % mod;
    cout << ret << endl;
    return 0;
}

I - Random Walk On Tree

待补。

J - Tree Constructer

简要题意:

输入一棵 n n n 个结点的树,构造序列 a i a_i ai ,使得当边 ( x , y ) (x, y) (x,y) 存在当且仅当 a x  or  a y = 2 60 − 1 a_x~\text{or}~a_y = 2^{60} - 1 ax or ay=2601

1 ≤ n ≤ 100 , 0 ≤ a i < 2 60 1 \le n \le 100, 0 \le a_i \lt 2^{60} 1n100,0ai<260

解题思路:

树是二分图,假设左部点数较少,首先可以拿出两位,让左部点为 01 01 01 ,右部点为 10 10 10 ,那么可以保证左部与左部、右部与右部没有边。其次,对每个左部顶点 u u u 拿出一位,若右部点 v v v u u u 没有边,则该位都为 0 0 0 ,否则 v v v 该位置为 1 1 1 ,注意左部除了 u u u 以外的点该位也要为 1 1 1 。需要的位数最多为 2 + ⌊ n 2 ⌋ 2 + \lfloor\cfrac{n}{2}\rfloor 2+2n

参考代码:
#include
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 5;

vector<int> pi[2];
int G[maxn][maxn], col[maxn];
int n;

void dfs(int u, int f, int d){

    pi[d].emplace_back(u), col[u] = d;
    for(int v = 1; v <= n; ++v){

        if(v == f) continue;
        if(G[u][v]) dfs(v, u, d ^ 1);
    }
}

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for(int i = 1; i < n; ++i){

        int u, v; cin >> u >> v;
        G[u][v] = G[v][u] = 1;
    }
    dfs(1, 0, 0);
    vector<ll> ans(n + 1, (1ll << 60) - 1);
    if(pi[0].size() > pi[1].size()) swap(pi[0], pi[1]);
    for(auto &v : pi[0]) ans[v] ^= 0b01;
    for(auto &v : pi[1]) ans[v] ^= 0b10;
    int d = 2;
    for(auto &u : pi[0]){

        ans[u] ^= 1ll << d;
        for(int v = 1; v <= n; ++v){

            if(!G[u][v] && col[v] != col[u]) ans[v] ^= 1ll << d;
        }
        ++d;
    }
    // for(int i = 1; i <= n; ++i){

    //     cout << bitset<60>(ans[i]).to_string() << endl;
    // }
    for(int i = 1; i <= n; ++i){

        cout << ans[i] << " \n"[i == n];
    }
    return 0;
}

K - Kth Query

简要题意:

给一个长度为 n n n 的序列 a i a_i ai ,定义 f ( a , S , K ) f(a, S, K) f(a,S,K) 为满足 b i = a i ⊕ S b_i = a_i \oplus S bi=aiS b i b_i bi 序列的第 K K K 小值。 Q Q Q 次询问,每次询问给定 L , R , K L, R, K L,R,K ,求 min ⁡ S = L R f ( a , S , K ) \min_{S=L}^{R} f(a, S, K) minS=LRf(a,S,K)

1 ≤ n , Q ≤ 1 0 5 , 0 ≤ a i < 2 30 , 0 ≤ L ≤ R < 2 30 , 1 ≤ K ≤ n 1 \le n, Q \le 10^5, 0 \le a_i \lt 2^{30}, 0 \le L \le R \lt 2^{30}, 1 \le K \le n 1n,Q105,0ai<230,0LR<230,1Kn

解题思路:

建立一棵字典树,将 a i a_i ai 插入。假设询问没有 L , R L, R L,R 限制,即求任意 S S S 下的第 K K K 小,可以预处理 k t h ( u , k ) kth(u, k) kth(u,k) u u u 子树下 S S S 剩余位任意取值情况下的第 k k k 小值。转移时枚举当前 S S S 的取值,如果该位是 1 1 1 ,那么 c h ( u , 1 ) ch(u, 1) ch(u,1) 子树与 S S S 的异或值显然小于 c h ( u , 0 ) ch(u, 0) ch(u,0) 的,第 k k k 小来源根据子树大小讨论即可。

对于有 L , R L, R L,R 限制的,枚举高位限制可以得到在某一位后 S S S 的值没有限制情况下的答案,所有情况取最小。

参考代码:
#include
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = maxn * 30;
const int inf = 1 << 30;
 
vector<vector<int>> kth;
int a[maxn], nxt[maxm][2], siz[maxm];
int n, q, cnt;
const int lg = 29;
 
int add(){
 
    return ++cnt;
}
 
void init(){
 
    cnt = -1; add();
}
 
void insert(int x){
 
    int p = 0; ++siz[p];
    for(int i = lg; i >= 0; --i){
 
        int t = (x >> i) & 1;
        if(!nxt[p][t]) nxt[p][t] = add();
        p = nxt[p][t], ++siz[p];
    }
}
 
void dfs(int u, int d){
 
    int v0 = nxt[u][0], v1 = nxt[u][1];
    if(!v0 && !v1){
 
        kth[u][1] = 0;
        return;
    }
    if(v0) dfs(v0, d - 1);
    if(v1) dfs(v1, d - 1);
    for(int i = 1; i <= siz[u]; ++i){
 
        kth[u][i] = inf;
        if(siz[v0] >= i) kth[u][i] = min(kth[u][i], kth[v0][i]);
        else kth[u][i] = min(kth[u][i], (1 << d) ^ kth[v1][i - siz[v0]]);
        if(siz[v1] >= i) kth[u][i] = min(kth[u][i], kth[v1][i]);
        else kth[u][i] = min(kth[u][i], (1 << d) ^ kth[v0][i - siz[v1]]);
    }
}
 
void build(){
 
    init();
    for(int i = 1; i <= n; ++i){
 
        insert(a[i]);
    }
    kth.resize(cnt + 1);
    for(int i = 0; i <= cnt; ++i){
 
        kth[i].resize(siz[i] + 1);
    }
    for(int i = 0; i <= n; ++i){
 
        kth[0][i] = inf;
    }
    dfs(0, lg);
}
 
int query(int S, int k, int lim, int u = 0, int d = lg){
 
    if(d < lim) return kth[u][k];
    int t = (S >> d) & 1;
    if(nxt[u][t] && siz[nxt[u][t]] >= k) return query(S, k, lim, nxt[u][t], d - 1);
    else{
 
        if(nxt[u][t]) k -= siz[nxt[u][t]];
        return (1 << d) ^ query(S, k, lim, nxt[u][t ^ 1], d - 1);
    }
}
 
int main(){
     
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i <= n; ++i){
 
        cin >> a[i];
    }
    build();
    while(q--){
 
        int l, r, k; cin >> l >> r >> k;
        int p = lg;
        while(p >= 0 && (l >> p & 1) == (r >> p & 1)) --p;
        int ret = min(query(l, k, 0), query(r, k, 0));
        for(int i = p - 1; i >= 0; --i){
 
            if((l >> i) & 1) continue;
            ret = min(ret, query(l ^ (1 << i), k, i));
        }
        for(int i = p - 1; i >= 0; --i){
 
            if((~r >> i) & 1) continue;
            ret = min(ret, query(r ^ (1 << i), k, i));
        }
        cout << ret << "\n";
    }
    return 0;
}

 
上面的代码是 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn) 的,可以优化到 O ( n l o g n ) O(nlogn) O(nlogn)

#include
using namespace std;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int maxm = maxn * 30;
const int inf = 1 << 30;
 
vector<vector<int>> kth;
int a[maxn], nxt[maxm][2], siz[maxm];
int n, q, cnt;
const int lg = 29;
 
int add(){
 
    return ++cnt;
}
 
void init(){
 
    cnt = -1; add();
}
 
void insert(int x){
 
    int p = 0; ++siz[p];
    for(int i = lg; i >= 0; --i){
 
        int t = (x >> i) & 1;
        if(!nxt[p][t]) nxt[p][t] = add();
        p = nxt[p][t], ++siz[p];
    }
}
 
void dfs(int u, int d){
 
    int v0 = nxt[u][0], v1 = nxt[u][1];
    if(!v0 && !v1){
 
        kth[u][1] = 0;
        return;
    }
    if(v0) dfs(v0, d - 1);
    if(v1) dfs(v1, d - 1);
    for(int i = 1; i <= siz[u]; ++i){
 
        kth[u][i] = inf;
        if(siz[v0] >= i) kth[u][i] = min(kth[u][i], kth[v0][i]);
        else kth[u][i] = min(kth[u][i], (1 << d) ^ kth[v1][i - siz[v0]]);
        if(siz[v1] >= i) kth[u][i] = min(kth[u][i], kth[v1][i]);
        else kth[u][i] = min(kth[u][i], (1 << d) ^ kth[v0][i - siz[v1]]);
    }
}
 
void build(){
 
    init();
    for(int i = 1; i <= n; ++i){
 
        insert(a[i]);
    }
    kth.resize(cnt + 1);
    for(int i = 0; i <= cnt; ++i){
 
        kth[i].resize(siz[i] + 1);
    }
    for(int i = 0; i <= n; ++i){
 
        kth[0][i] = inf;
    }
    dfs(0, lg);
}
 
int query(int x, int k, int lim, int flg){
 
    auto walk = [](int &p, int &k, int t) -> int{
 
        if(nxt[p][t] && siz[nxt[p][t]] >= k) {
             
            p = nxt[p][t];
            return 0;
        }
        else{
 
            if(nxt[p][t]) k -= siz[nxt[p][t]];
            p = nxt[p][t ^ 1];
            return 1;
        }
    };
    int p = 0, ret = inf, S = 0;
    for(int i = lg; i >= 0; --i){
 
        int t = (x >> i) & 1;
        if(i < lim && t == flg){
 
            int tp = p, tk = k, tS = S;
            if(walk(tp, tk, t ^ 1)) tS ^= 1 << i;
            ret = min(ret, tS ^ kth[tp][tk]);
        }
        if(walk(p, k, t)) S ^= 1 << i;
    }
    ret = min(ret, S ^ kth[p][k]);
    return ret;
}
 
int main(){
     
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i <= n; ++i){
 
        cin >> a[i];
    }
    build();
    while(q--){
 
        int l, r, k; cin >> l >> r >> k;
        int p = lg;
        while(p >= 0 && (l >> p & 1) == (r >> p & 1)) --p;
        int ret = min(query(l, k, p, 0), query(r, k, p, 1));
        cout << ret << "\n";
    }
    return 0;
}

L - Bit Sequence

简要题意:

给定 L , m L, m L,m ,以及一个长度为 m m m 01 01 01 数组 a i a_i ai 。定义 f ( x ) f(x) f(x) x x x 二进制 1 1 1 的个数。求有多少数 x ∈ [ 0 , L ] x \in [0, L] x[0,L] ,满足 ∀ i ∈ [ 0 , m − 1 ] , f ( x + i )   m o d   2 = a i \forall i \in [0, m - 1], f(x + i)~mod~ 2 = a_i i[0,m1],f(x+i) mod 2=ai

多组数据, 1 ≤ T ≤ 1000 , 1 ≤ m ≤ 100 , 0 ≤ L ≤ 1 0 18 , a i ∈ { 0 , 1 } 1 \le T \le 1000, 1 \le m \le 100, 0 \le L \le 10^{18}, a_i \in \{0, 1\} 1T1000,1m100,0L1018,ai{0,1}

解题思路:

由于 m m m 比较小,枚举 L L L 的低 7 7 7 位,那么 L + i L + i L+i 最多向高位进 1 1 1 ,考虑高位满足的数进行合并。定义 d p ( p o s , l i m , s u m , r e v ) dp(pos, lim, sum, rev) dp(pos,lim,sum,rev) 为当前枚举到 p o s pos pos 位、数位是否有限制、高位 1 1 1 个数的奇偶性、低位进 1 1 1 时高位 s u m sum sum 是否改变,数位 dp \text{dp} dp 到低 7 7 7 位时进行枚举。

参考代码:
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;

ll dp[64][2][2][2], L;
int a[105], b[64], m;

int cal(int lim, int sum, int rev){

    int up = lim ? L % 128 : 127, ret = 0;
    for(int i = 0; i <= up; ++i){

        int flg = 1;
        for(int j = 0; j < m && flg; ++j){

            if(i + j < 128) flg &= __builtin_parity(i + j) ^ sum == a[j];
            else flg &= __builtin_parity(i + j - 128) ^ sum ^ rev == a[j];
        }
        ret += flg;
    }
    // cout << lim << " " << sum << " " << rev << " " << ret << endl;
    return ret;
}

ll dfs(int pos, int lim, int sum, int rev){

    ll &ret = dp[pos][lim][sum][rev];
    if(~ret) return ret;
    if(pos <= 6) return ret = cal(lim, sum, rev);
    ret = 0;
    int up = lim ? b[pos] : 1;
    for(int i = 0; i <= up; ++i){

        ret += dfs(pos - 1, lim && i == up, sum ^ i, i ? rev ^ 1 : 1);
    }
    return ret;
}

ll solve(){

    memset(dp, -1, sizeof dp);
    int len = 0;
    for(ll x = L; x; x >>= 1) b[len++] = x & 1;
    if(L == 0) b[len++] = 0;
    return dfs(len - 1, 1, 0, 1);
}

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){

        cin >> m >> L;
        for(int i = 0; i < m; ++i){

            cin >> a[i];
        }
        cout << solve() << "\n";
    }
    return 0;
}

M - Cook Pancakes!

简要题意:

n n n 个煎饼和 k k k 个锅,每个锅只能在一分钟时间煎一面,问煎完的最少时间。

1 ≤ n , k ≤ 100 1 \le n, k \le 100 1n,k100

解题思路:

k ≥ n k \ge n kn 答案为 2 2 2 。否则按编号顺序先煎完正面、再煎反面,由于 k < n k \lt n k<n ,不会冲突,答案就是 ⌈ 2 n k ⌉ \lceil\cfrac{2n}{k}\rceil k2n

参考代码:
#include
using namespace std;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int n, k; cin >> n >> k;
    cout << max(2, (2 * n + k - 1) / k) << endl;
    return 0;
}

你可能感兴趣的:(赛后补题专栏)