比赛杂题选讲「2022.9.30 ~ 2022.10.19」

Preface

image

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

本篇博客元素有:网格图;Su_Zipei is always here;猜道路;C;挑战;寻宝 (treasure);找 (a);tothecrazyoneswishusgoodluck;树上询问;网络;


CSP-S模拟15

这个题我咕了一周多,后来花了整整一个上午调试+下午的一小会时间终于 A

首先考虑暴力怎么打,显然是枚举这个 K*K 矩阵的右下角,然后扫一遍他的四周和内部统计答案,最后取最大值。时间复杂度大概是 O(N4),显然超时。有 50pts 的好成绩。但是sandom就这样A了,我不理解

当然了你得先预处理一下联通块的大小和每个点的 belong (他属于哪个联通块)。

考虑如何优化这个过程。首先明确答案的组成:对于一个 K*K 矩形,他的答案可以分为 [①]『K×K 块内 x 的个数』+ [②]『完全被包含在 K×K 块内的联通块大小』+ [③]『K×K 块四周的联通块大小』。

画个图理解:

image

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

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

image

在同一行中,发现当矩阵右移时并不需要整个的扫一遍,相反可以继承上一个状态的一些答案。因为③的答案可以直接两个 O(n)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

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

这个题大佬们都基本场切了,但是有一个部分分我想说一下。

考虑到他给出的这个矩阵中一部分肯定是作为原边权出现的,那么可以对于上三角矩阵枚举哪些边是作为原边权出现的,选出来跑个 Floyd,跑出来再和整个矩阵比较判断是否最短路都一致,相应选择是否更新答案。

考虑到时限有 2000ms,可以跑一个随机选边,就不爆搜了。数据小的时候随便跑就行。配合输出 1 可以拿到 56pts 的好成绩。

正解就不说了。

随机化·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日

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日

寻宝 (treasure)

简单写一下思路

首先大力 BFS 记录联通块,然后对于传送门先不 BFS 过去,将所有联通块搜好之后用传送门在联通块之间连边,表示这两个联通块是单向联通的,然后由于最多只有 100 个传送门即最多只有 100 条边,所以对于每次询问一遍 dfs 即可。有环的话可以 Tarjan 缩个点,但没必要,开个 bitset 记录是否访问过就可以了?

代码不放了,实现较为简单。

2022NOIP A层联测7 10月11日

找(a)

一个拆柿子签到题。我不知道我当时脑子怎么抽了没拆柿子甚至都没写,反而去找了一个粑粑规律还假了,喜提暴力 30pts。估计是规律题做傻了。

简单拆个柿子

我们要求的是:

i=1nj=1n(xixj)2+(yiyj)2[ij]

i 不用 j (伏笔)

拆开得

i=1nj=1nxi2+xj2+yi2+yj22xixj2yiyj[ij]

然后就发现 xj2yj2 可以 O(n) 预处理了,xjyj 也可以 O(n) 预处理,于是就搞掉一个

sumxfi=1nxi2sumyfi=1nyi2sumxi=1nxisumyi=1nyi,则原柿子变为:

桥豆麻袋!i 不用 j!我shab了!下面这个柿子直接跳过不用看。


i=1n(n1)xi2+(n1)yi2+(sumxfxi2)+(sumyfyi2)2xi(sumxxi)2yi(sumyyi)

至于为什么这么多减号是因为原柿子里那个 [ij]


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

i=1nn(xi2+yi2)+sumxf+sumyf2xisumx2yisumy

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日

tothecrazyones

是的题目中本来就没有空格(

小清新博弈论。但是赛时看到博弈论直接跑路了。赛后发现这玩意简单的一匹。

先明确一个事情,就最优策略而言,那就是李四取哪堆石子那么张三就取哪堆石子,以达到把这些堆石子的状态给还原为李四操作之前的状态。(就是说让 ai 每次减少一个 (x+y) 而不是只减少一个 y 什么的)

首先你考虑这么一个堆:有石子 8 个,x=2y=3。显然李四最后取不了了张三获胜。让石子数量增加到 13 个。你会发现仍然是张三获胜。类似地,你发现 182328... 都是这个结果,那么判定获胜条件就可以只关心 aimod(x+y) 即可。

既然取了模,那么此时每堆石子的个数 ai<x+y

你考虑什么样的情况下张三必败。显然是当这一堆石子李四可以取,但是张三取不了。刚才我们说了,我们只需要关心 aimod(x+y) 后的情况,那么当一个堆的石子李四能取张三取不了,那么李四取了之后又该张三取,但是张三没得取了(因为 (ai<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;
}

wishusgoodluck

是的题目仍然是本来就没有空格(

这个题我没 A,但是用 winqizhi 算法成功水到了 83pts 的高分。我在这里说一下这个题的 winqizhi 算法咋打。

首先一个显然的假贪心是,枚举每一行每一列,如果翻过来更优那就翻。显然是 fake 的。你考虑到这个题你反正也不会做,打暴力都费劲,那你就不如打假贪心。

实际上不能叫假贪心吧,真的就是 winqizhi 算法,怎么这么高的我也说不清楚。

你首先枚举每一行,分别算一下这一行翻和不翻的答案,然后判断是否要翻过来。注意不是递归地枚举而是循环地枚举。时间复杂度是 O(n) 的而不是 O(2n)

算翻和不翻的答案依旧是假贪心。循环每一列。如果翻过来更优那就翻过来,不优的话就不翻。不用管翻过来更优以后万一不优,你翻就完了,你打的是假贪心。

为了让这个矩阵更玄学一点,在最开始枚举每一行的时候给他多循环几次。不用复原矩阵,直接用上一次修改后的矩阵。

然后你就这么写发现你三个样例都过了。

有点像随机化了(

引用 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

树上询问

这个题的做法挺有意思。根据题意,设询问的为端点为 l rlca 为他们的最近公共祖先。

根据题意我们可以把询问拆成左链和右链,其中定义左链为从 llca 之间的这一条链,右链定义为从 lcar 的这一条链。

不妨先讨论左链。

根据题意我们要求的是 depldepx=x 的点的个数。移项发现可以变为 depl=depx+x,而右边的数可以预处理。

于是就有了一个朴素的思想:预处理 depx+x 将其扔进一个二维前缀和数组里,对于 sumi,j,第一维 i 表示是哪个点,第二维表示 depj+j 这个,数组内存的值为:从这个点往上的这一条链上,depj+j 这个的出现次数。

那么查询的时候只需要查左链上值为 depl 的个数即可。即 suml,deplsumfalca,depl

但是这样的得分甚至比暴力分+子任务分治的分都少,因为数组开不下。

考虑将询问离线下来,在刚才的二维数组做法中,对于每一组询问,我们要做的就是计算:suml,deplsumfalca,depl。我们可以将所有询问扔到每一个点里,就比如告诉点 l 让他查一个 suml,depl 给加到这个询问的 ans 里,告诉 falca 查一个 sumfalca,depl 并将其到这个询问的 ans 里(也就是把 sumfalca,depl 给加进这个询问的 ans 里)。

所以就可以边 dfs 边计算答案了,不需要 sum 数组,开一个桶记录即可,不过这样就要保证当前始终是一条链而没有分支。

考虑如何保证从当前搜到的点到根节点是一条链,其实不需要其他的神奇方法,只需要在 dfs 的时候刚搜到时更新桶,在要 return 的时候撤销操作即可。手模发现这样是正确的。看不懂的话可以看代码理解。

右链同理,推柿子是这样的:depldeplca+depxdeplca=x,移项得 2deplcadepl=depxx,也就是右链的桶里放 depxx。开两个桶分别维护左右链的个数即可。

代码实现较为简单。有一个小细节需要注意,由于算左链的时候减的是 falca,也就是说 lca 的贡献已经算过一次了,不应重复计算,右链的时候减的应该是 lca 而不是 falca。有点像树上差分。

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;
}

这个题懒得改了, 但是想说一下部分分。拿了 60pts

众所周知,暴力出奇迹。所以大胆猜想有多个解(废话),然后 O(2m) 枚举每个平衡器向上还是向下爆搜就完了。然后直接拿到 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 @   char_phi  阅读(19)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示