比赛杂题选讲「2022.9.30 ~ 2022.10.19」

\(\text{Preface}\)

image

我觉得殷教说的很对。如果说强行让我写之前咕掉的总结我觉得意义不大,而且我大多数题也都忘了再写思路不一定对而且有亿点敷衍,所以我就写其中一部分吧

本篇博客元素有:网格图;\(\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\) 块四周的联通块大小』。

画个图理解:

image

那个黑框框框住的就是 K*K 矩形。

在这个图里边我按照刚才的标号给他重新标一下,就是这个样子:

image

在同一行中,发现当矩阵右移时并不需要整个的扫一遍,相反可以继承上一个状态的一些答案。因为③的答案可以直接两个 \(O(n)\)\(\text{for}\)循环扫出来,所以考虑维护①和②的答案。

整三个答案变量分别算三个部分的答案。我给他们命名的分别是 xans isdans outans

isdans 指的就是 inside ans

具体地,先弄一个 used 数组标识一下当前这个联通块是否已经贡献过答案了,避免重复贡献。再整一个 isd 数组(inside)标识这个联通块是否被完全包含在 K*K 矩形中。

对于每一行,先爆扫第一个矩形,记录一下答案,然后开始移动。

在移动之前首先把左边那一列对块内部的贡献删去,显然的当左边这一列被删的时候出现了 . 就说明这个 . 所属的联通块不完全被包含在块内了。更新相关信息。

然后移动矩形,将 used 清空(因为重新统计即可,不用费劲继承上一个),先扫矩形的四周,记录谁已经贡献过了。

最后再扫新加进来的这一列。因为刚才已经记录了谁被用过了,如果说当前新加进来的这一列中有个 .usedfalse,这说明他是完全被包含在块内的了。记得更新他的 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}\)。估计是规律题做傻了。

简单拆个柿子

我们要求的是:

\[\sum_{i = 1}^{n} {\sum_{j=1}^{n} {(x_i - x_j)^2 + (y_i - y_j)^2 [i \neq j] }} \]

\(i\) 不用 \(\neq j\) (伏笔)

拆开得

\[\sum_{i = 1}^{n} { \sum_{j=1}^{n} { x_i^2 + x_j^2 + y_i^2 + y_j^2 - 2x_ix_j - 2y_iy_j [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了!下面这个柿子直接跳过不用看。


\[\sum_{i=1}^{n} { (n-1)x_i^2 + (n-1)y_i^2 + (sumxf-x_i^2) + (sumyf-y_i^2) - 2x_i(sumx-x_i) - 2y_i(sumy - y_i) } \]

至于为什么这么多减号是因为原柿子里那个 \([i \neq j]\)


但是发现原式并不需要 \([i \neq j]\),因为 \(i = j\) 时算出来是 \(0\) (后知后觉),所以整个柿子其实就是:

\[\sum_{i=1}^{n} { n(x_i^2 + y_i^2) + sumxf + sumyf - 2x_isumx - 2y_isumy } \]

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;
}
posted @ 2022-10-11 21:51  char_phi  阅读(13)  评论(0编辑  收藏  举报