做题笔记 2022.12.24
Merry Xmas.
CF1615F LEGOndary Grandmaster 展开目录
题目给出的操作是可以将两个相邻两个相同的位反转,这样的操作不好实现,我们考虑将原序列的所有偶数位反转,操作就变为了交换两个相邻的数,注意到交换相同的数是无效的,这样就处理了在转化前对两个不等的数进行操作这样的不合法行为。
现在,对于所有合法的起始状态 - 终止状态,我们要求出从起始状态变换为终止状态的操作次数之和。
首先考虑对于固定的起始状态
很显然,我们将
为了计算答案,考虑 dp,设
考虑如何转移,注意到如果
这是一个子矩形和,二维前缀和优化即可。
细节有点多。
注意代码的实现中对 0 和 1 的处理与题解是相反的,或者说,代码是在对 0 进行匹配,而题解很显然是对 1 进行匹配。
Code
- namespace Main{
- const int N = 2005;
- const int MOD = 1e9 + 7;
- int t, n, a[N], b[N], la[N], lb[N];
- ll f[N][N], g[N][N];
- bool p1[N], p2[N];
- string s1, s2;
- void Main(){
- input(t);
- while(t--){
- input(n);
- cin >> s1 >> s2;
- for(int i = 1; i <= n; i++){
- a[i] = s1[i-1] == '?' ? -1 : s1[i-1] - '0';
- b[i] = s2[i-1] == '?' ? -1 : s2[i-1] - '0';
- if((i & 1) && a[i] != -1) a[i] = 1 - a[i];
- if((i & 1) && b[i] != -1) b[i] = 1 - b[i];
- }
- la[0] = lb[0] = 1;
- for(int i = 1; i <= n; i++){
- if(a[i] == 0) la[i] = i, p1[i] = true;
- else la[i] = la[i-1], p1[i] = p1[i-1];
- if(b[i] == 0) lb[i] = i, p2[i] = true;
- else lb[i] = lb[i-1], p2[i] = p2[i-1];
- }
- for(int i = 1; i <= n; i++){
- for(int j = 1; j <= n; j++){
- if(a[i] == 1 || b[j] == 1) f[i][j] = g[i][j] = 0;
- else{
- f[i][j] = ((!p1[i-1] && !p2[j-1]) + f[i-1][j-1] - f[la[i-1]-1][j-1] - f[i-1][lb[j-1]-1] + f[la[i-1]-1][lb[j-1]-1] + MOD + MOD) % MOD;
- g[i][j] = f[i][j] * abs(i - j) % MOD;
- g[i][j] = (g[i][j] + g[i-1][j-1] - g[la[i-1]-1][j-1] - g[i-1][lb[j-1]-1] + g[la[i-1]-1][lb[j-1]-1] + MOD + MOD) % MOD;
- }
- f[i][j] = (f[i][j] + f[i-1][j] + f[i][j-1] - f[i-1][j-1] + MOD) % MOD;
- g[i][j] = (g[i][j] + g[i-1][j] + g[i][j-1] - g[i-1][j-1] + MOD) % MOD;
- }
- }
- // for(int i = 1; i <= n; i++){
- // for(int j = 1; j <= n; j++) cerr << g[i][j] - g[i-1][j] - g[i][j-1] + g[i-1][j-1] << ' ';
- // cerr << endl;
- // }
- // cerr << la[n] << ' ' << lb[n] << endl;
- write((g[n][n] - g[la[n]-1][n] - g[n][lb[n]-1] + g[la[n]-1][lb[n]-1] + MOD + MOD) % MOD);
- puts("");
- }
- return;
- }
- } // namespace
CF1659E AND-MEX Walk 展开目录
给定一张无向连通图,对于一条路径,考虑其边权序列
容易发现,答案不会超过 2,因为不断进行与运算相当于是每次 reset 了若干个二进制位,那么不可能同时出现 01 和 10,即 1 和 2。
如果答案为 0,那么我们经过的所有边应该至少有一个二进制位全部为 1,于是我们枚举每个二进制位,将所有该位为 1 的边拉出来用并查集合并,如果
如果答案为 1,相当于最低位被 reset 时,还有其他的二进制位为 1,换言之,我们在经过第一条最低位为 0 之前经过的所有边,应当至少有一个二进制位全部为 1(包括现在经过的这条边),在实现时,我们构造一个新点,编号为 0,所有边权最低位为 0 的边的两个端点与 0 合并,然后类似的,枚举每个二进制位,将所有该位为 1 的边拉出来合并,查询时如果
否则,答案为 2。
Code
- namespace Main{
- const int N = 100005;
- const int L = 30;
- int n, m, q;
- struct dsu{
- int fa[N];
- void init(int n) { for(int i = 1; i <= n; i++) fa[i] = i; }
- int get(int x){
- if(fa[x] == x) return x;
- return fa[x] = get(fa[x]);
- }
- void unite(int x, int y){
- fa[get(x)] = get(y);
- }
- }d[L], z[L];
- void Main(){
- input(n, m);
- for(int i = 0; i < 30; i++) d[i].init(n), z[i].init(n);
- for(int i = 0, u, v, w; i < m; i++){
- input(u, v, w);
- for(int j = 0; j < 30; j++){
- if(w >> j & 1) d[j].unite(u, v), z[j].unite(u, v);
- }
- if(~w & 1) for(int j = 1; j < 30; j++) z[j].unite(u, 0), z[j].unite(v, 0);
- }
- input(q);
- while(q--){
- int u, v; bool flag = false, f2 = false;
- input(u, v);
- for(int j = 29; j > 0; j--){
- if(d[j].get(u) == d[j].get(v)){
- flag = true;
- }
- if(z[j].get(u) == z[j].get(0)){
- f2 = true;
- }
- }
- if(flag || d[0].get(u) == d[0].get(v)) puts("0");
- else if(f2) puts("1");
- else puts("2");
- }
- return;
- }
- } // namespace
CF1734E Rectangular Congruence 展开目录
说实话这个题我做起来挺吃力的,我甚至一度认为
题目给出的限制是
这个式子长得有点像四边形不等式,这样的形式其实很不利于我们观察,考虑将其变形为
首先,限制条件全部形如同一行中的两数之差意味着如果我们将一行中的数全部加上或减去同一个数,并不会影响整个矩阵的合法性,那么题目中对对角线的限制就解决了,构造的最后再将对角线平移为正确的值即可。
考虑一列一列的构造,首先很自然地想到按照如下的方式摆放前两列:
- 0 0
- 0 1
- 0 2
- 0 3
- 0 4
那么,第三列怎么办呢,可以考虑干脆将每一行相邻两个数的差固定下来,整个矩阵填充如下:
- 0 0 0 0 0
- 0 1 2 3 4
- 0 2 4 1 3
- 0 3 1 4 2
- 0 4 3 2 1
形式化的,第
接下来考虑正确性,
Code
- namespace Main{
- const int N = 355;
- int n, a[N];
- void Main(){
- input(n);
- for(int i = 0; i < n; i++) input(a[i]);
- for(int i = 0; i < n; i++){
- for(int j = 0; j < n; j++) write(((a[i] - i * i + i * j) % n + n) % n), putchar(' ');
- puts("");
- }
- return;
- }
- } // namespace
Xmas Tree feat. wgx


本作品采用 知识共享署名 - 非商业性使用 - 相同方式共享 4.0 国际许可协议 进行许可。