比赛杂题选讲「2022.9.30 ~ 2022.10.19」
\(\text{Preface}\)
我觉得殷教说的很对。如果说强行让我写之前咕掉的总结我觉得意义不大,而且我大多数题也都忘了再写思路不一定对而且有亿点敷衍,所以我就写其中一部分吧
本篇博客元素有:网格图;\(\text{Su_Zipei is always here}\);猜道路;\(\text{C}\);挑战;寻宝 (\(\text{treasure}\));找 (\(\text{a}\));\(\text{tothecrazyones}\);\(\text{wishusgoodluck}\);树上询问;网络;
CSP-S模拟15
\(网格图\)
这个题我咕了一周多,后来花了整整一个上午调试+下午的一小会时间终于 \(\text{A}\) 掉
首先考虑暴力怎么打,显然是枚举这个 K*K
矩阵的右下角,然后扫一遍他的四周和内部统计答案,最后取最大值。时间复杂度大概是 \(O(N^4)\),显然超时。有 \(\text{50pts}\) 的好成绩。但是sandom就这样A了,我不理解
当然了你得先预处理一下联通块的大小和每个点的 belong
(他属于哪个联通块)。
考虑如何优化这个过程。首先明确答案的组成:对于一个 K*K
矩形,他的答案可以分为 [①]『\(K \times K\) 块内 x
的个数』\(+\) [②]『完全被包含在 \(K \times K\) 块内的联通块大小』\(+\) [③]『\(K \times K\) 块四周的联通块大小』。
画个图理解:
那个黑框框框住的就是 K*K
矩形。
在这个图里边我按照刚才的标号给他重新标一下,就是这个样子:
在同一行中,发现当矩阵右移时并不需要整个的扫一遍,相反可以继承上一个状态的一些答案。因为③的答案可以直接两个 \(O(n)\) 的 \(\text{for}\)循环扫出来,所以考虑维护①和②的答案。
整三个答案变量分别算三个部分的答案。我给他们命名的分别是 xans
isdans
outans
isdans
指的就是 inside ans
具体地,先弄一个 used
数组标识一下当前这个联通块是否已经贡献过答案了,避免重复贡献。再整一个 isd
数组(inside)标识这个联通块是否被完全包含在 K*K
矩形中。
对于每一行,先爆扫第一个矩形,记录一下答案,然后开始移动。
在移动之前首先把左边那一列对块内部的贡献删去,显然的当左边这一列被删的时候出现了 .
就说明这个 .
所属的联通块不完全被包含在块内了。更新相关信息。
然后移动矩形,将 used
清空(因为重新统计即可,不用费劲继承上一个),先扫矩形的四周,记录谁已经贡献过了。
最后再扫新加进来的这一列。因为刚才已经记录了谁被用过了,如果说当前新加进来的这一列中有个 .
的 used
为 false
,这说明他是完全被包含在块内的了。记得更新他的 isd
数组。
如此移动即可。细节还行,不是特别多,调试时间这么长的原因是之前的思路太麻烦了,而且后来听新的思路又理解错了一次。。
不过代码还是自己写的,没有贺。
建议也别贺我的,因为我估计看了我写的代码得吐,不删注释有将近五百行(
Code
#include <iostream>
#include <bitset>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 505
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
找到问题了,是used[]的锅,检查在往右扫的时候used数组的变化是否正确,应该是这里挂了
锐评今日多校:一个题都不会改(
我又来改这个题了(
两百多行烂码
最终A掉已经接近五百行了。。。
*/
int n, K, L, R, kuainum, final_ans, isdans, outans, xans;
int belong[N*N], sz[N*N];
bitset<N*N> used, isd;
char s[N][N];
struct Poi{int x, y;};
struct Poi q[N*N*2];
inline void bfs(int x, int y, int ID){
L = 0, R = -1;
q[++ R] = (Poi){x, y}; belong[(x-1)*n + y] = ID; sz[ID] = 1;
struct Poi fw;
while (L <= R){
fw = q[L ++], x = fw.x, y = fw.y;
// 往左拓展
if (s[x][y-1] == '.' and belong[(x-1)*n + (y-1)] == 0 and y > 1)
q[++ R] = (Poi){x, y-1}, belong[(x-1)*n + (y-1)] = ID, sz[ID] ++;;
// 往右
if (s[x][y+1] == '.' and belong[(x-1)*n + (y+1)] == 0 and y < n)
q[++ R] = (Poi){x, y+1}, belong[(x-1)*n + (y+1)] = ID, sz[ID] ++;
// 往上
if (s[x-1][y] == '.' and belong[(x-2)*n + y] == 0 and x > 1)
q[++ R] = (Poi){x-1, y}, belong[(x-2)*n + y] = ID, sz[ID] ++;
// 往下
if (s[x+1][y] == '.' and belong[x*n + y] == 0 and x < n)
q[++ R] = (Poi){x+1, y}, belong[x*n + y] = ID, sz[ID] ++;
}
}
/*
5 2
..xxx
xx.xx
x.xxx
x...x
xxxx.
md,继续hack
嗯?!还是对的?!
我找正确代码对拍灞
我囸,大样例都过了
好吧我自己造数据
5 2
..xxx
x..xx
.xx..
..x.x
xxxx.
5 2
xxxx.
x.x..
.....
xxx.x
x..xx
5 2
.x..x
xx.xx
.xxxx
xxxxx
xxx.x
5 2
xx.xx
.xxxx
.xx.x
.x.xx
.xx..
好了这一组有锅:
5 2
..x..
xxxxx
xxxxx
xx..x
x.x.x
答案是方格右下角处于(3, 4)时 (3, 3)
还过不了
又来一组数据
5 2
x..x.
.xxxx
.x.xx
x.x..
.x.xx
答案在(3, 3)
*/
/*inline void Debug(){
cerr << "used: ";
for (re i = 1 ; i <= 6 ; ++ i)
cerr << used[i] << _;
Dl;
cerr << "still: ";
for (re i = 1 ; i <= 6 ; ++ i)
cerr << still[i] << _;
Dl;
cerr << "dld: ";
for (re i = 1 ; i <= 6 ; ++ i)
cerr << dld[i] << _;
Dl;
}*/
inline int id(int x, int y){return (x-1)*n+y;}
inline void work(){
cin >> n >> K;
for (re i = 1 ; i <= n ; ++ i)
cin >> s[i]+1;
/*cerr << n << _ << K << '\n';// 实在没办法了,这题拖了一周多了(套WA的数据点)
for (re i = 1 ; i <= n ; ++ i)
cerr << s[i]+1 << '\n';*/
for (re i = 1 ; i <= n ; ++ i){
for (re j = 1 ; j <= n ; ++ j){
if (s[i][j] == '.' and belong[(i-1)*n+j] == 0)
kuainum ++, bfs(i, j, kuainum);
}
}
/*for (re i = 1 ; i <= n ; ++ i){// 无锅
for (re j = 1 ; j <= n ; ++ j)
cerr << belong[(i-1)*n+j] << _;
Dl;
}
cerr << kuainum << '\n';
for (re i = 1 ; i <= kuainum ; ++ i)
cerr << sz[i] << _;
Dl; Dl;*/
// 核心思想又出错了!我囸!
// 完全在块内 + 四周 + 块内x的个数
// 好家伙样例WA了
for (re x = K, i, ii, j, jj ; x <= n ; ++ x){
// 先爆扫第一个
outans = isdans = xans = 0;
used = 0; isd = 0;
// 先扫边界并打上标记
// 右边
j = K+1;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
outans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 上边和下边
i = x-K, ii = x+1;
for (j = 1 ; j <= K ; ++ j){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
outans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
if (s[ii][j] == '.' and used[belong[id(ii, j)]] == false)
outans += sz[belong[id(ii, j)]], used[belong[id(ii, j)]] = true;
}
// 扫块内
for (i = x-K+1 ; i <= x ; ++ i){
for (j = 1 ; j <= K ; ++ j){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false and isd[belong[id(i, j)]] == false)// 完全被包含在块内
isdans += sz[belong[id(i, j)]], isd[belong[id(i, j)]] = true;
else if (s[i][j] != '.')
xans ++;
}
}
/*if (x == 3){
cerr << isdans << _ << outans << _ << xans << '\n';
cerr << "used: ";
for (re i = 1 ; i <= 5 ; ++ i)
cerr << used[i] << _;
Dl;
cerr << "isd: ";
for (re i = 1 ; i <= 5 ; ++ i)
cerr << isd[i] << _;
Dl;
}*/
final_ans = MAX(final_ans, isdans+outans+xans);
// 然后先把左边一列的isdans和xans删去
j = 1;
for (i = x-K+1 ; i <= x ; ++ i){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false and isd[belong[id(i, j)]] == true)
isdans -= sz[belong[id(i, j)]], isd[belong[id(i, j)]] = false;
else if (s[i][j] != '.')
xans --;
}
// 往右移
for (re y = K+1 ; y <= n ; ++ y){
used = 0; outans = 0;
// 先把外边一层标记上
// 左边和右边
j = y-K, jj = y+1;
for (i = x-K+1 ; i <= x ; ++ i){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
outans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
if (s[i][jj] == '.' and used[belong[id(i, jj)]] == false)
outans += sz[belong[id(i, jj)]], used[belong[id(i, jj)]] = true;
}
// 上边和下边
i = x-K, ii = x+1;
for (j = y-K+1 ; j <= y ; ++ j){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
outans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
if (s[ii][j] == '.' and used[belong[id(ii, j)]] == false)
outans += sz[belong[id(ii, j)]], used[belong[id(ii, j)]] = true;
}
// 搜新加入的列
j = y;
for (i = x-K+1 ; i <= x ; ++ i){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false and isd[belong[id(i, j)]] == false)
isdans += sz[belong[id(i, j)]], isd[belong[id(i, j)]] = true;
else if (s[i][j] != '.')
xans ++;
}
final_ans = MAX(final_ans, isdans+outans+xans);
// 把左边那一列的贡献给删掉
j = y-K+1;
for (i = x-K+1 ; i <= x ; ++ i){
if (s[i][j] == '.' and used[belong[id(i, j)]] == false and isd[belong[id(i, j)]] == true)
isdans -= sz[belong[id(i, j)]], isd[belong[id(i, j)]] = false;
else if (s[i][j] != '.')
xans --;
}
}
}
cout << final_ans << '\n';
// 仅仅需要把移动一格之后被删的边的点给加上,新加的边的点给减去,然后扫一遍四个框统计外部答案即可。我。。。
/*for (re x = K, i, j ; x <= n ; ++ x){// 右下角在第几行
// 直接暴扫第一个
isdans = 0; tmpans = 0; used = 0;
for (i = x-K+1 ; i <= x ; ++ i){
for (j = 1 ; j <= K ; ++ j){// 当前块内的
if (s[i][j] == '.')
isdans --;// insideans记录里边有几个是'.',之后减去这个就好了
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
}
}
// 扫右边
j = K + 1;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 扫上边
i = x - K;
for (j = 1 ; j <= K ; ++ j)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 扫下边
i = x + 1;
for (j = 1 ; j <= K ; ++ j)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
final_ans = MAX(final_ans, tmpans+isdans+K*K);
if (x == 3)
cerr << "begin: " << isdans << _ << tmpans << '\n';
// 块向右移动
for (re y = K+1 ; y <= n ; ++ y){
// 扫左边变为相邻的边
used = 0; tmpans = 0;
j = y - K;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.')
isdans ++;
// 扫右边新加入的边
j = y;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.')
isdans --;
// 暴力扫块的四个边
// 左边
j = y - K;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 右边
j = y + 1;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 上边
i = x - K;
for (j = y-K+1 ; j <= y ; ++ j)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
// 下边
i = x + 1;
for (j = y-K+1 ; j <= y ; ++ j)
if (s[i][j] == '.' and used[belong[id(i, j)]] == false)
tmpans += sz[belong[id(i, j)]], used[belong[id(i, j)]] = true;
if (x == 3)
cerr << "y = " << y << " 時,ループ内: " << isdans << _ << tmpans << '\n';
final_ans = MAX(final_ans, tmpans+isdans+K*K);
}
cerr << x << _ << "真ん中のfinal_ans: " << final_ans << '\n';
}*/
/*for (re x = K ; x <= n ; ++ x){
used = 0;
tmpans = K*K;// 先暴力扫第一个
for (re i = x-K ; i <= x+1 ; ++ i){
for (re j = 0 ; j <= K+1 ; ++ j){
if ((i == x-K and j == 0) or (i == x-K and j == K+1) or (i == x+1 and j == 0) or (i == x+1 and j == K+1))
continue;
ider = (i-1)*n + j;
/*if (x == 3)
cerr << "遍历到了点(" << i << ", " << j << "):" << '\n' << "before-tmpans: " << tmpans << '\n';*//*
if (s[i][j] == '.' and used[belong[ider]] == false)
tmpans += sz[belong[ider]], used[belong[ider]] = true;
/*if (x == 3)
cerr << "Mid-tmpans: " << tmpans << '\n';*//*
if (s[i][j] == '.' and i <= x and j <= K and i >= x-K+1 and j >= 1)
tmpans --;
/*if (x == 3)
cerr << "after-tmpans: " << tmpans << '\n';*//*
// Dl; Dl;
}
}
/*if (x == 3)
Debug();*/
/*if (x == 3)
cerr << tmpans << '\n';*/// 没问题
/*
final_ans = MAX(final_ans, tmpans);
// cerr << "x: " << x << '\n';
// cerr << "大before-ans: " << tmpans << _ << final_ans << '\n';
for (re y = K+1, i, j ; y <= n ; ++ y){// 类似莫队地向右移
// 我觉得我重构一遍比较好,细节太多了
// 我听sandom说了一嘴,我人傻了
// 对阿,我直接扫一遍四个边不就行了,复杂度是对的
// 我囸!
// 仅仅需要把移动一格之后被删的边的点给加上,新加的边的点给减去,然后扫一遍四个框统计外部答案即可
/*dld = 0;
// 最左边被删除的边
j = y - K - 1;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and dld[belong[id(i, j)]] == false and used[belong[id(i, j)]] == false)
dld[belong[id(i, j)]] = true, tmpans -= sz[belong[id(i, j)]];
// 稍微靠左的边
// 首先他变成了自由边之后原来扣掉的要加上
j = y-K;
for (i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.')
tmpans ++;
*/
/*still = 0; dld = 0;
j = y - K;// 稍微靠左的边
for (re i = x-K+1 ; i <= x ; ++ i){
if (s[i][j] == '.')
tmpans ++, still[belong[(i-1)*n + j]] = true;
}
if (x == 3)
cerr << "稍微靠左的边 " << tmpans << '\n', Debug();
// 新的左上角
if (s[x-K][y-K+1] == '.' and
j = y - K - 1;// 被淘汰的左边
for (re i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and still[belong[(i-1)*n + j]] == false)
tmpans -= sz[belong[(i-1)*n + j]], used[belong[(i-1)*n + j]] = false, dld[belong[(i-1)*n + j]] = true;
if (x == 3)
cerr << "被淘汰的左边 " << tmpans << '\n', Debug();
// 左上角
if (s[x-K][y-K] == '.' and still[belong[(x-K-1)*n + y-K]] == false and dld[belong[(x-K-1)*n + y-K]] == false)
tmpans -= sz[belong[(x-K-1)*n + y-K]], used[belong[(x-K-1)*n + y-K]] = false, dld[belong[(x-K-1)*n + y-K]] = true;
if (x == 3)
cerr << "左上角 " << tmpans << '\n', Debug();
// 左下角
if (s[x+1][y-K] == '.' and still[belong[x*n + y-K]] == false and dld[belong[x*n + y-K]] == false)
tmpans -= sz[belong[x*n + y-K]], used[belong[x*n + y-K]] = false, dld[belong[x*n + y-K]] = true;
if (x == 3)
cerr << "左下角 " << tmpans << '\n', Debug();
// 处理好左边了
// 稍微靠右的边
j = y;
for (re i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.')
tmpans --, used[belong[(i-1)*n+j]] = true;
if (x == 3)
cerr << "稍微靠右的边 " << tmpans << '\n', Debug();
// 右上角
if (s[x-K][y] == '.' and used[belong[(x-K-1)*n + y]] == false)
tmpans += sz[belong[(x-K-1)*n + y]], used[belong[(x-K-1)*n + y]] = true;
if (x == 3)
cerr << "右上角 " << tmpans << '\n', Debug();
// 右下角
if (s[x+1][y] == '.' and used[belong[x*n + y]] == false)
tmpans += sz[belong[x*n + y]], used[belong[x*n + y]] = true;
if (x == 3)
cerr << "右下角 " << tmpans << '\n', Debug();
// 最靠右的边
j = y+1;
for (re i = x-K+1 ; i <= x ; ++ i)
if (s[i][j] == '.' and used[belong[(i-1)*n + j]] == false)
tmpans += sz[belong[(i-1)*n + j]], used[belong[(i-1)*n + j]] = true;
if (x == 3)
cerr << "最靠右的边 " << tmpans << '\n', Debug();
// 终于都处理完了
final_ans = MAX(final_ans, tmpans);
Dl; Dl; Dl; Dl; Dl;*//*
}
// cerr << "大after-ans: " << tmpans << _ << final_ans << '\n';
// Dl; Dl; Dl; Dl; Dl;
}*/
// cerr << '\n' << "Final: " << final_ans << '\n';
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
Fastio_setup();
work();
return GMY;
}
国庆のsurprise
\(\text{Su_Zipei is always here}\)
大力分块差分即可。没啥好讲的,但是在出现次数上开数组,挺妙的。
然后查询出现次数的时候直接边更新边查,也比较妙(也可能是因为我比较菜所以觉得妙(
有点卡常,加个火车头
Code
%:pragma GCC target("avx")
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
%:pragma GCC optimize("-fgcse")
%:pragma GCC optimize("-fgcse-lm")
%:pragma GCC optimize("-fipa-sra")
%:pragma GCC optimize("-ftree-pre")
%:pragma GCC optimize("-ftree-vrp")
%:pragma GCC optimize("-fpeephole2")
%:pragma GCC optimize("-ffast-math")
%:pragma GCC optimize("-fsched-spec")
%:pragma GCC optimize("unroll-loops")
%:pragma GCC optimize("-falign-jumps")
%:pragma GCC optimize("-falign-loops")
%:pragma GCC optimize("-falign-labels")
%:pragma GCC optimize("-fdevirtualize")
%:pragma GCC optimize("-fcaller-saves")
%:pragma GCC optimize("-fcrossjumping")
%:pragma GCC optimize("-fthread-jumps")
%:pragma GCC optimize("-funroll-loops")
%:pragma GCC optimize("-fwhole-program")
%:pragma GCC optimize("-freorder-blocks")
%:pragma GCC optimize("-fschedule-insns")
%:pragma GCC optimize("inline-functions")
%:pragma GCC optimize("-ftree-tail-merge")
%:pragma GCC optimize("-fschedule-insns2")
%:pragma GCC optimize("-fstrict-aliasing")
%:pragma GCC optimize("-fstrict-overflow")
%:pragma GCC optimize("-falign-functions")
%:pragma GCC optimize("-fcse-skip-blocks")
%:pragma GCC optimize("-fcse-follow-jumps")
%:pragma GCC optimize("-fsched-interblock")
%:pragma GCC optimize("-fpartial-inlining")
%:pragma GCC optimize("no-stack-protector")
%:pragma GCC optimize("-freorder-functions")
%:pragma GCC optimize("-findirect-inlining")
%:pragma GCC optimize("-fhoist-adjacent-loads")
%:pragma GCC optimize("-frerun-cse-after-loop")
%:pragma GCC optimize("inline-small-functions")
%:pragma GCC optimize("-finline-small-functions")
%:pragma GCC optimize("-ftree-switch-conversion")
%:pragma GCC optimize("-foptimize-sibling-calls")
%:pragma GCC optimize("-fexpensive-optimizations")
%:pragma GCC optimize("-funsafe-loop-optimizations")
%:pragma GCC optimize("inline-functions-called-once")
%:pragma GCC optimize("-fdelete-null-pointer-checks")
#include <iostream>
#include <cmath>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 100005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
马八∑
我囸,被卡常了
大样例跑了三秒多
*/
int n, Q, Opt, blocklen=3300, blocknum, final_ans;
int w[N], t[N];
int cnt[33][N];
int a[33][33][N];
inline void Block_Divide(){
blocknum = (n+blocklen-1) / blocklen;
// sleep(10);
// cerr << blocklen << _ << blocknum << '\n';
for (re i = 1 ; i <= blocknum ; ++ i){
int Lf = (i-1) * blocklen + 1, Rt = MIN(n, blocklen*i);
cerr << Lf << _ << Rt << '\n';
for (re j = 1 ; j <= n ; ++ j)
cnt[i][j] = cnt[i-1][j];
for (re j = Lf ; j <= Rt ; ++ j)// 差分
cnt[i][w[j]] ++;
}
for (re l = 1 ; l <= blocknum ; ++ l){
for (re r = l ; r <= blocknum ; ++ r){
for (re col = 1 ; col <= n ; ++ col)
a[l][r][cnt[r][col]-cnt[l-1][col]] ++;
for (re col = 1 ; col <= n ; ++ col)
a[l][r][col] += a[l][r][col-1];
}
}
}
inline int Query(int l, int r, int K){
int res = 0, bl = (l+blocklen-1) / blocklen, br = (r+blocklen-1) / blocklen;
if (br - bl <= 1){
for (re i = l ; i <= r ; ++ i)
t[w[i]] ++, res += (t[w[i]] == K);// 好方法,直接在加入桶的时候查询,不用再扫整个值域了
for (re i = l ; i <= r ; ++ i)
t[w[i]] = 0;
}
else {
res = a[bl+1][br-1][n] - a[bl+1][br-1][K-1];// 差分
int Rtl = bl * blocklen, Lfr = (br-1) * blocklen + 1;
for (re i = l ; i <= Rtl ; ++ i)
t[w[i]] ++, res += ((t[w[i]] + cnt[br-1][w[i]] - cnt[bl][w[i]]) == K);
for (re i = Lfr ; i <= r ; ++ i)
t[w[i]] ++, res += ((t[w[i]] + cnt[br-1][w[i]] - cnt[bl][w[i]]) == K);
for (re i = l ; i <= Rtl ; ++ i)
t[w[i]] = 0;
for (re i = Lfr ; i <= r ; ++ i)
t[w[i]] = 0;
}
return res;
}
inline void work(){
cin >> n >> Q >> Opt;
for (re i = 1 ; i <= n ; ++ i)
cin >> w[i];
Block_Divide();
int l1, r1, K1, l, r, K;
while (Q --){
cin >> l1 >> r1 >> K1;
l = MIN((l1 + final_ans*Opt - 1) % n + 1, (r1 + final_ans*Opt - 1) % n + 1);
r = MAX((l1 + final_ans*Opt - 1) % n + 1, (r1 + final_ans*Opt - 1) % n + 1);
K = (K1 + final_ans*Opt - 1) % n + 1;
if (l > r)
swap(l, r);
// cerr << l << _ << r << _ << K << '\n'; sleep(4);
final_ans = Query(l, r, K);
cout << final_ans << '\n';
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(suzipei, a);
#endif
Fastio_setup();
work();
return GMY;
}
CSP-S模拟16
\(猜道路\)
这个题大佬们都基本场切了,但是有一个部分分我想说一下。
考虑到他给出的这个矩阵中一部分肯定是作为原边权出现的,那么可以对于上三角矩阵枚举哪些边是作为原边权出现的,选出来跑个 \(\text{Floyd}\),跑出来再和整个矩阵比较判断是否最短路都一致,相应选择是否更新答案。
考虑到时限有 \(\text{2000ms}\),可以跑一个随机选边,就不爆搜了。数据小的时候随便跑就行。配合输出 \(-1\) 可以拿到 \(\text{56pts}\) 的好成绩。
正解就不说了。
随机化·$\text{56pts}$
#include <iostream>
#include <ctime>
#include <cstring>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 305
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
不会找环的屑ch_p来写T1
差分约束desu
in fact
这最短路,肯定有一些边是本来就被选上了的
那么就随机选边,跑Floyd验证
但是极限数据下似乎只能随机十次
不对,别说是极限数据了,29%的数据也是n<=300吧
我囸
打这么随机的玩意...真的能行吗...
样例能过,笑死我了((
震惊!大样例跑了甚至80次+!
能拿多少分捏?大约有零分灞
*/
int n;
long long final_ans(1145141145141919810), tmpans;
long long tim;
long long a[N][N], dis[N][N];
inline void Floyd(){
for (re i = 1 ; i <= n ; ++ i){
for (re j = 1 ; j <= n ; ++ j){
if (i == j)
continue;
for (re k = 1 ; k <= n ; ++ k){
if (i == k or j == k)
continue;
if (dis[i][j] > dis[i][k]+dis[k][j])
dis[i][j] = dis[i][k]+dis[k][j];
}
}
}
}
/*
3
0 1 3
1 0 2
3 2 0
*/
inline void work(){
cin >> n;
for (re i = 1 ; i <= n ; ++ i)
for (re j = 1 ; j <= n ; ++ j)
cin >> a[i][j];
while (true){
if ((clock()-tim) * 1000 >= 1900 * CLOCKS_PER_SEC)
break;
tmpans = 0; memset(dis, 0x3f, sizeof(dis));
for (re i = 1 ; i <= n ; ++ i)
for (re j = i+1 ; j <= n ; ++ j)
if ((rand() & 3) == 0)
dis[i][j] = dis[j][i] = a[i][j], tmpans += a[i][j];// cerr << i << _ << j << '\n';
Floyd();
/*for (re i = 1 ; i <= n ; ++ i){
for (re j = 1 ; j <= n ; ++ j)
cerr << dis[i][j] << _;
Dl;
}*/
for (re i = 1 ; i <= n ; ++ i)
for (re j = i+1 ; j <= n ; ++ j)
if (dis[i][j] != a[i][j])
{tmpans = 1145141145141919810; break;}
final_ans = MIN(final_ans, tmpans);
// Dl; Dl; Dl;
// cerr << "胡萝贝" << '\n';
}
if (final_ans == 1145141145141919810)
cout << -1 << '\n';
else
cout << final_ans << '\n';
}
// #define IXINGMY
char_phi main(){
tim = clock();
srand(time(0));
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
Fastio_setup();
work();
return GMY;
}
正解
#include <iostream>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 305
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
*/
int n;
long long final_ans;
char deleted[N][N];
long long a[N][N];
inline void Floyd(){
for (re k = 1 ; k <= n ; ++ k){
for (re i = 1 ; i <= n ; ++ i){
if (i == k)
continue;
for (re j = 1 ; j <= n ; ++ j){
if (j == i or j == k)
continue;
if (a[i][j] > a[i][k]+a[k][j])
goto CANT;
if (a[i][j] == a[i][k]+a[k][j])
deleted[i][j] = deleted[j][i] = true;
}
}
}
return ;
CANT:{cout << -1 << '\n'; exit(0);}
}
inline void work(){
cin >> n;
for (re i = 1 ; i <= n ; ++ i)
for (re j = 1 ; j <= n ; ++ j)
cin >> a[i][j];
Floyd();
for (re i = 1 ; i <= n ; ++ i)
for (re j = i+1 ; j <= n ; ++ j)
if (deleted[i][j] == false)
final_ans += a[i][j];
cout << final_ans << '\n';
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
Fastio_setup();
work();
return GMY;
}
2022NOIP A层联测3 10月5日
\(\text{C}\)
整个歪解,从低到高依次确定答案。
首先任何数异或 \(0\) 等于他本身,所以第一位答案就直接找到整个序列中字典序最小的字母,然后把这个字母所在的所有位置作为『候选的 \(k\)』扔到一个数组里。
然后确定第二位,把候选的 \(k\) 们依次异或出的位置中,选出该位置字符字典序最小的那一个,并将所有能得到这个字符的 \(k\) 们再次扔到数组里,类似的确定更高位直到『候选的 \(k\)』只剩一个或答案已经确定完毕。(应该等到『候选的 \(k\)』只剩一个了就可以 break
了?)
Code
#pragma GCC optimize(2)
#include <iostream>
#include <cmath>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define NN 262155
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
一个一个地选,选出“值得我枚举的k”,一直选直到只剩一个k。
*/
int n, mn;
char str[NN], ans[NN];
int a[NN], tmp[NN], s[NN];
/*
2
acba
3
bcbaaabb
*/
inline void work(){
cin >> n; n = (1 << n) - 1;
cin >> str;
for (re i = 0 ; i <= n ; ++ i)
s[i] = (int)str[i];
// 第一次选是0,0^k = k,所以第一次只需要找出来整个序列里字典序最小的字母即可
mn = 1145141919;
for (re i = 0 ; i <= n ; ++ i)
if (s[i] < mn)
mn = s[i];
for (re i = 0 ; i <= n ; ++ i)
if (s[i] == mn)
a[++ a[0]] = i;
/*cerr << (char)mn << _ << a[0] << '\n';
cerr << "a[i]: ";
for (re i = 1 ; i <= a[0] ; ++ i)
cerr << a[i] << _;
Dl;*/
for (re i = 1 ; i <= n ; ++ i){
if (a[0] == 1)
break;
// cerr << i << '\n';
mn = 1145141919; tmp[0] = 0;
for (re j = 1 ; j <= a[0] ; ++ j)// cerr << "s[a[j]^i]: " << (char)s[a[j]^i] << '\n';
if (s[a[j] ^ i] < mn)
mn = s[a[j] ^ i];
for (re j = 1 ; j <= a[0] ; ++ j)
if (s[a[j] ^ i] == mn)
tmp[++ tmp[0]] = a[j];
// cerr << "repmn:|" << (char)mn << '\n';
a[0] = tmp[0];
for (re j = 1 ; j <= tmp[0] ; ++ j)
a[j] = tmp[j];
// Dl; Dl; Dl;
}
// cerr << a[0] << _ << a[1] << '\n';
for (re i = 0 ; i <= n ; ++ i)
ans[i] = str[i^a[1]];
cout << ans << '\n';
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
Fastio_setup();
work();
return GMY;
}
2022NOIP A层联测5 10月8日
\(挑战\)
赛时干了三个多小时没干出来
2022NOIP A层联测6 10月10日
\(\text{寻宝 (treasure)}\)
简单写一下思路
首先大力 \(\text{BFS}\) 记录联通块,然后对于传送门先不 \(\text{BFS}\) 过去,将所有联通块搜好之后用传送门在联通块之间连边,表示这两个联通块是单向联通的,然后由于最多只有 \(100\) 个传送门即最多只有 \(100\) 条边,所以对于每次询问一遍 \(\text{dfs}\) 即可。有环的话可以 \(\text{Tarjan}\) 缩个点,但没必要,开个 bitset
记录是否访问过就可以了?
代码不放了,实现较为简单。
2022NOIP A层联测7 10月11日
\(\text{找(a)}\)
一个拆柿子签到题。我不知道我当时脑子怎么抽了没拆柿子甚至都没写,反而去找了一个粑粑规律还假了,喜提暴力 \(\text{30pts}\)。估计是规律题做傻了。
简单拆个柿子
我们要求的是:
\(i\) 不用 \(\neq j\) (伏笔)
拆开得
然后就发现 \(x_j^2\) 和 \(y_j^2\) 可以 \(O(n)\) 预处理了,\(\sum{x_j}\) 和 \(\sum{y_j}\) 也可以 \(O(n)\) 预处理,于是就搞掉一个 \(\sum\)。
设 \(sumxf\) 为 \(\sum_{i=1}^{n} { x_i^2 }\),\(sumyf\) 为 \(\sum_{i=1}^{n} { y_i^2 }\),\(sumx\) 为 \(\sum_{i=1}^{n} { x_i }\),\(sumy\) 为 \(\sum_{i=1}^{n} { y_i }\),则原柿子变为:
桥豆麻袋!\(i\) 不用 \(\neq j\)!我shab了!下面这个柿子直接跳过不用看。
至于为什么这么多减号是因为原柿子里那个 \([i \neq j]\)。
但是发现原式并不需要 \([i \neq j]\),因为 \(i = j\) 时算出来是 \(0\) (后知后觉),所以整个柿子其实就是:
md我要不写这个总结我还真没发现我当时写麻烦了(
下面这个代码是按照麻烦那一版写的,简单版自己代码实现就行了又不难。
code
#pragma GCC optimize(2)
#include <cstdio>
#include <cctype>
#define GMY (520&1314)
#define char_phi signed
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define P 998244353
#define mod %
#define N 200005
#define ll __int128
using namespace std;
// inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
我就该早把柿子拆开
*/
inline ll read(){
ll x = 0; char c;
while (!isdigit(c = getchar()));
do {x = (x << 3) + (x << 1) + (c & 15);} while (isdigit(c = getchar()));
return x;
}
void ot(ll x){
if (x < 0) putchar('-'), x = -x;
if (x > 9) ot(x/10);
putchar((x%10)+48);
}
ll n, final_ans, sumxf, sumyf, sumx, sumy;
struct Poi {ll x, y;};
struct Poi a[N];
#define pf(x) ((x) * (x) mod P)
inline void work(){
n = read();
for (ll i = 1, x, y ; i <= n ; ++ i)
{x = read(), y = read(); x = x mod P, y = y mod P; a[i] = (Poi){x, y}; sumxf = (sumxf + pf(x)) mod P, sumyf = (sumyf + pf(y)) mod P, sumx = (sumx + x) mod P, sumy = (sumy + y) mod P;}
for (ll i = 1 ; i <= n ; ++ i){
/*final_ans = ((sumxf - a[i].x * a[i].x mod P + P) mod P + (sumyf - a[i].y * a[i].y mod P + P)) mod P;// 好吧我还是取模吧
final_ans = (final_ans + a[i].x * a[i].x mod P * (n-1) mod P) mod P, final_ans = (final_ans + a[i].y * a[i].y mod P * (n-1) mod P) mod P;
final_ans = (final_ans - a[i].x * a[i].x mod P + P) mod P, final_ans = (final_ans - a[i].y * a[i].y mod P + P) mod P;
final_ans = (final_ans - 2 * a[i].x * (sumx-a[i].x+P) mod P + P) mod P, final_ans = (final_ans - 2 * a[i].y * (sumy-a[i].y+P) mod P + P) mod P;
ot(final_ans), putchar('\n');*/
final_ans = ((sumxf-pf(a[i].x)+P) mod P + (sumyf-pf(a[i].y)+P)) mod P;
final_ans = (final_ans + pf(a[i].x) * (n-1) mod P) mod P;
final_ans = (final_ans + pf(a[i].y) * (n-1) mod P) mod P;
final_ans = (final_ans - 2 * a[i].x mod P * (sumx-a[i].x+P) mod P + P) mod P;
final_ans = (final_ans - 2 * a[i].y mod P * (sumy-a[i].y+P) mod P + P) mod P;
ot(final_ans), putchar('\n');
}
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
// Fastio_setup();
work();
return GMY;
}
2022NOIPA层联测8 10月14日
\(\text{tothecrazyones}\)
\(↑\) 是的题目中本来就没有空格(
小清新博弈论。但是赛时看到博弈论直接跑路了。赛后发现这玩意简单的一匹。
先明确一个事情,就最优策略而言,那就是李四取哪堆石子那么张三就取哪堆石子,以达到把这些堆石子的状态给还原为李四操作之前的状态。(就是说让 \(a_i\) 每次减少一个 \((x+y)\) 而不是只减少一个 \(y\) 什么的)
首先你考虑这么一个堆:有石子 \(8\) 个,\(x = 2,y = 3\)。显然李四最后取不了了张三获胜。让石子数量增加到 \(13\) 个。你会发现仍然是张三获胜。类似地,你发现 \(18、23、28...\) 都是这个结果,那么判定获胜条件就可以只关心 \(a_i \bmod (x+y)\) 即可。
既然取了模,那么此时每堆石子的个数 \(a_i < x+y\)。
你考虑什么样的情况下张三必败。显然是当这一堆石子李四可以取,但是张三取不了。刚才我们说了,我们只需要关心 \(a_i \bmod (x+y)\) 后的情况,那么当一个堆的石子李四能取张三取不了,那么李四取了之后又该张三取,但是张三没得取了(因为 \((a_i < x+y)!\)),所以张三败。
如果你看到这里不理解了,你要记住,每个人一次可以取多个堆的石子,而且当某堆石子没得取了那他就会去取其他堆。
再看看有没有别的张三必败的情形。发现当石子数目取模后,张三一个石子都取不了时,那么张三当然是必败的。因为每次取 \((x+y)\) 等到取完之后还是张三先手,这时候他没石子取了就败了。
然后拿这些测一下大样例,然后就过了。
博弈论还挺好玩(
code
#pragma GCC optimize(2)
#include <iostream>
#define GMY (520&1314)
#define char_phi signed
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define re register int
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
*/
char fl;
int n, x, y, T;
int a[N];
inline void work(){
cin >> n >> x >> y; fl = false;
for (re i = 1 ; i <= n ; ++ i)
{cin >> a[i]; a[i] %= (x+y);}
for (re i = 1 ; i <= n ; ++ i)
if (a[i] >= x)
{goto Continuer;}
cout << 0 << '\n'; return ;
Continuer:{}
for (re i = 1 ; i <= n ; ++ i){
if (a[i] >= y and a[i] < x)
{cout << 0 << '\n'; return ;}
else
fl = true;
}
if (fl == false)
{cout << 0 << '\n'; return ;}
cout << 1 << '\n';
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(tothecrazyones_ex1, a);
#endif
Fastio_setup();
cin >> T;
while (T --)
work();
return GMY;
}
\(\text{wishusgoodluck}\)
\(↑\) 是的题目仍然是本来就没有空格(
这个题我没 \(\text{A}\),但是用 \(\text{winqizhi}\) 算法成功水到了 \(\text{83pts}\) 的高分。我在这里说一下这个题的 \(\text{winqizhi}\) 算法咋打。
首先一个显然的假贪心是,枚举每一行每一列,如果翻过来更优那就翻。显然是 \(\text{fake}\) 的。你考虑到这个题你反正也不会做,打暴力都费劲,那你就不如打假贪心。
实际上不能叫假贪心吧,真的就是 \(\text{winqizhi}\) 算法,怎么这么高的我也说不清楚。
你首先枚举每一行,分别算一下这一行翻和不翻的答案,然后判断是否要翻过来。注意不是递归地枚举而是循环地枚举。时间复杂度是 \(O(n)\) 的而不是 \(O(2^n)\)。
算翻和不翻的答案依旧是假贪心。循环每一列。如果翻过来更优那就翻过来,不优的话就不翻。不用管翻过来更优以后万一不优,你翻就完了,你打的是假贪心。
为了让这个矩阵更玄学一点,在最开始枚举每一行的时候给他多循环几次。不用复原矩阵,直接用上一次修改后的矩阵。
然后你就这么写发现你三个样例都过了。
有点像随机化了(
引用 \(\text{Rolling_Star}\) 大佬的一句话:
玄之又玄,众妙之门。
code
/*
做总结
T1 博弈论 不会
T2 某结论题
T3 某构造题 我觉得在哪做过
T4 神奇树形dp?
开题顺序 T3 -> T2 -> T1 -> T4
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <iostream>
#define GMY (520&1314)
#define char_phi signed
#define re register int
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout)
#define Endl cout << '\n'
#define _ ' '
#define Dl cerr << '\n';
#define DMARK cerr << "###"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 25
#define M 100005
using namespace std;
inline void Fastio_setup(){ ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL); }
/*
我挺好奇这个题正解是啥
n这么小,是状压䥯
有一个假的贪心:whiletrue循环次数,然后如果将他翻转了之后1的个数变多那就翻转,反之不翻
既然这个题不会,那么为什么不这么做呢
我曹!怎么三个样例都过了???????
我不能梨解
*/
/*
3 4
0110
0101
1110
10 10
1010001100
0000011011
1100110011
1000010011
1100100011
1001011010
0000100011
1001001100
0000100111
1001000111
*/
int n, m, final_ans;
char s[N][M];
int a[N][M];
inline int check(){
int res(0), xia, shang;
for (re j = 1 ; j <= m ; ++ j){
xia = shang = 0;
for (re k = 1 ; k <= n ; ++ k)
xia += (a[k][j] == 1), shang += (a[k][j] == 0);
if (xia > shang){// 给他翻个面
for (re k = 1 ; k <= n ; ++ k)
a[k][j] ^= 1;
}
res += MIN(xia, shang);// 尽量都朝上
}
// res为朝下的个数
return res;
}
inline void work(){
cin >> n >> m;
for (re i = 1 ; i <= n ; ++ i){
cin >> s[i]+1;
for (re j = 1 ; j <= m ; ++ j)
a[i][j] = (s[i][j] == '1');
}
final_ans = 1145141919;
for (re times = 1 ; times <= 7 ; ++ times){
for (re line = 1, rs1, rs2 ; line <= n ; ++ line){
// 不翻
rs1 = check();
// 翻
for (re j = 1 ; j <= m ; ++ j)
a[line][j] ^= 1;
rs2 = check();
final_ans = MIN(final_ans, MIN(rs1, rs2));
if (rs1 < rs2){// 不翻
for (re j = 1 ; j <= m ; ++ j)
a[line][j] ^= 1;// 翻回去
}
}
}
cout << final_ans << '\n';
}
// #define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(a, a);
#endif
Fastio_setup();
work();
return GMY;
}
2022NOIP A层联测12
\(\text{树上询问}\)
这个题的做法挺有意思。根据题意,设询问的为端点为 \(l\) \(r\),\(lca\) 为他们的最近公共祖先。
根据题意我们可以把询问拆成左链和右链,其中定义左链为从 \(l\) 到 \(lca\) 之间的这一条链,右链定义为从 \(lca\) 到 \(r\) 的这一条链。
不妨先讨论左链。
根据题意我们要求的是 \(dep_l - dep_x = x\) 的点的个数。移项发现可以变为 \(dep_l = dep_x + x\),而右边的数可以预处理。
于是就有了一个朴素的思想:预处理 \(dep_x + x\) 将其扔进一个二维前缀和数组里,对于 \(sum_{i,j}\),第一维 \(i\) 表示是哪个点,第二维表示 \(dep_j + j\) 这个值,数组内存的值为:从这个点往上的这一条链上,\(dep_j+j\) 这个值的出现次数。
那么查询的时候只需要查左链上值为 \(dep_l\) 的个数即可。即 \(sum_{l, dep_l} - sum_{fa_{lca}, dep_l}\)。
但是这样的得分甚至比暴力分+子任务分治的分都少,因为数组开不下。
考虑将询问离线下来,在刚才的二维数组做法中,对于每一组询问,我们要做的就是计算:\(sum_{l, dep_l} - sum_{fa_{lca}, dep_l}\)。我们可以将所有询问扔到每一个点里,就比如告诉点 \(l\) 让他查一个 \(sum_{l, dep_l}\) 给加到这个询问的 \(ans\) 里,告诉 \(fa_{lca}\) 查一个 \(sum_{fa_{lca}, dep_l}\) 并将其减到这个询问的 \(ans\) 里(也就是把 \(-sum_{fa_{lca}, dep_l}\) 给加进这个询问的 \(ans\) 里)。
所以就可以边 \(\text{dfs}\) 边计算答案了,不需要 \(sum\) 数组,开一个桶记录即可,不过这样就要保证当前始终是一条链而没有分支。
考虑如何保证从当前搜到的点到根节点是一条链,其实不需要其他的神奇方法,只需要在 \(\text{dfs}\) 的时候刚搜到时更新桶,在要 \(\text{return}\) 的时候撤销操作即可。手模发现这样是正确的。看不懂的话可以看代码理解。
右链同理,推柿子是这样的:\(dep_l - dep_{lca} + dep_x - dep_{lca} = x\),移项得 \(2dep_{lca} - dep_l = dep_x - x\),也就是右链的桶里放 \(dep_x - x\)。开两个桶分别维护左右链的个数即可。
代码实现较为简单。有一个小细节需要注意,由于算左链的时候减的是 \(fa_{lca}\),也就是说 \(lca\) 的贡献已经算过一次了,不应重复计算,右链的时候减的应该是 \(lca\) 而不是 \(fa_{lca}\)。有点像树上差分。
code
#include <iostream>
#include <vector>
#include <map>
#define GMY (520&1314)
#define char_phi int
#define re register int
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define Endl cout << '\n'
#define _ ' '
#define Dl cerr << '\n'
#define DMARK cerr << "###"
#define N 300005
#define QUERY 300005
using namespace std;
inline void Fastio_setup(){ ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL); }
inline int MIN(int x, int y){ return ((x < y) ? (x) : (y)); }
inline int MAX(int x, int y){ return ((x > y) ? (x) : (y)); }
/*
我懂了 这么简单
1. dep[l] = dep[x]+x
2. dep[x] - x = 2*dep[lca] - dep[l]
离线下来记录一个lca和typ,直接搞
*/
int n, Q, star_cnt;
int head[N], pa[N], dep[N];
int fa[N][25];
struct star {int v, nxt;};
struct Question {int l, r, lca, ans;};
struct Poi {int id, typ, opt;};// 1l 2r
struct star e[N<<1];
struct Question q[QUERY];
map<int, int> sum[3];
vector<Poi> v[N];
inline void star_add(int u, int v){ e[++ star_cnt].v=v, e[star_cnt].nxt=head[u], head[u]=star_cnt; }
void dfs1(int x, int faer){
int k(0);
for (; fa[x][k] != 0 ; k ++)
fa[x][k+1] = fa[fa[x][k]][k];
pa[x] = k;
for (re i = head[x] ; i ; i = e[i].nxt){
int v = e[i].v;
if (v == faer)
continue;
dep[v] = dep[x]+1, fa[v][0] = x;
dfs1(v, x);
}
}
inline int LCA(int x, int y){
if (dep[x] < dep[y])
swap(x, y);
for (re i = pa[x] ; i >= 0 ; -- i)
if (dep[fa[x][i]] >= dep[y])
x = fa[x][i];
if (x == y)
return x;
for (re i = pa[x] ; i >= 0 ; -- i)
if (fa[x][i] != fa[y][i])
x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
inline int target(int opt, int l, int r, int lca){
if (opt == 1)
return dep[l];
else
return 2*dep[lca]-dep[l];
}
void dfs2(int x, int faer){
sum[1][dep[x]+x] ++; sum[2][dep[x]-x] ++;
int ql, qr;
for (auto ikun : v[x]){
ql = q[ikun.id].l, qr = q[ikun.id].r;
if (sum[ikun.opt].find(target(ikun.opt, ql, qr, q[ikun.id].lca)) == sum[ikun.opt].end())
continue;
q[ikun.id].ans += ikun.typ * sum[ikun.opt][target(ikun.opt, ql, qr, q[ikun.id].lca)];
}
for (re i = head[x] ; i ; i = e[i].nxt){
int v = e[i].v;
if (v == faer)
continue;
dfs2(v, x);
}
sum[1][dep[x]+x] --; sum[2][dep[x]-x] --;
}
inline void work(){
cin >> n >> Q;
for (re i = 1, uu, vv ; i <= n-1 ; ++ i)
{cin >> uu >> vv; star_add(uu, vv), star_add(vv, uu);}
dep[1] = 1;
dfs1(1, 0);
for (re i = 1, l, r, lca ; i <= Q ; ++ i){
cin >> l >> r; lca = LCA(l, r);
// cerr << "lca: " << lca << '\n';
q[i] = (Question){l, r, lca, 0};
v[l].push_back((Poi){i, 1, 1}), v[r].push_back((Poi){i, 1, 2});
v[fa[lca][0]].push_back((Poi){i, -1, 1}), v[lca].push_back((Poi){i, -1, 2});
}
dfs2(1, 0);
for (re i = 1 ; i <= Q ; ++ i)
cout << q[i].ans << '\n';
}
#undef int
#define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(query, query);
#endif
Fastio_setup();
work();
return GMY;
}
\(网络\)
这个题懒得改了, 但是想说一下部分分。拿了 \(\text{60pts}\)
众所周知,暴力出奇迹。所以大胆猜想有多个解(废话),然后 \(O(2^m)\) 枚举每个平衡器向上还是向下爆搜就完了。然后直接拿到 \(\text{60pts}\)。
乐死我了赛时懒得打暴力直接少60pts
code·TLE·60pts
#include <iostream>
#include <cstring>
#define GMY (520&1314)
#define char_phi int
#define re register int
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#define Endl cout << '\n'
#define _ ' '
#define Dl cerr << '\n'
#define DMARK cerr << "###"
#define N 500005
#define M 5000005
using namespace std;
inline void Fastio_setup(){ ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL); }
inline int MIN(int x, int y){ return ((x < y) ? (x) : (y)); }
inline int MAX(int x, int y){ return ((x > y) ? (x) : (y)); }
/*
我大概可以看出来T4是怎么搜的
应该是枚举某几根线是带电的?
*(问Eakang)*
草,这数据这么水吗
暴力出奇迹是吧(
*/
int n, m;
char dd[N];// 带电
int ans[M];
struct PHQ {int x, y;};
struct PHQ a[M];
inline void Print(){
cout << "YES" << '\n';
for (re i = 1 ; i <= m ; ++ i)
cout << ans[i];
Endl;
}
void XIN_team(int x, int num){
// cerr << "XIN_TEAM: " << x << _ << num << '\n';
if (num < n/2)
return ;
if (x == m+1)
{Print(); exit(0);}
int xi = a[x].x, yi = a[x].y;
// xi -> yi
ans[x] = 1;
if (dd[xi] == false)// false、[unknown]
XIN_team(x+1, num);
/*cerr << xi << _ << yi << ": 走1" << '\n', */
else {
if (dd[yi] == false){// true、false
dd[xi] = false, dd[yi] = true;/*cerr << xi << _ << yi << ": 走2" << '\n';*/
XIN_team(x+1, num);
dd[xi] = true, dd[yi] = false;
}
else {// true、true
dd[xi] = false;/*cerr << xi << _ << yi << ": 走3" << '\n';*/
XIN_team(x+1, num-1);
dd[xi] = true;
}
}
// yi -> xi
ans[x] = 0;
if (dd[yi] == false)// [unknown]、false
XIN_team(x+1, num);
else {
if (dd[xi] == false){// false、true
dd[xi] = true, dd[yi] = false;
XIN_team(x+1, num);
dd[xi] = false, dd[yi] = true;
}
else {// true、true
dd[yi] = false;
XIN_team(x+1, num-1);
dd[yi] = true;
}
}
}
inline void work(){
cin >> n >> m;
for (re i = 1, xi, yi ; i <= m ; ++ i)
{cin >> xi >> yi; a[i] = (PHQ){xi, yi};}
memset(dd, true, sizeof(dd));
XIN_team(1, n);
}
#undef int
#define IXINGMY
char_phi main(){
#ifdef IXINGMY
FBI_OPENTHEDOOR(network, network);
#endif
Fastio_setup();
work();
return GMY;
}