【题解】gym103495 (2021 JSCPC)

A. Spring Couplets

就是判断是否满足一下条件:

  • 对联对位上平下仄、上仄下平
  • 第一句对联的最后一个是仄

模拟即可。

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 30;
int T,n;
int a[NN],b[NN];

inline int read(){
	register char c = getchar();
	register int res = 0;
	while(!isdigit(c)) c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0',c = getchar();
	return res;
}

void solve(){
	n = read();
	for(int i = 1; i <= n; ++i) a[i] = read();
	for(int i = 1; i <= n; ++i) b[i] = read();
	for(int i = 1; i <= n; ++i)
		if((a[i] <= 2 && b[i] <= 2) || (a[i] >= 3 && b[i] >= 3)) return puts("NO"),void(0);
	if(a[n] <= 2) return puts("NO"),void(0);
	else return puts("YES"),void(0);
}

int main(){
	T = read();
	while(T--){
		solve();
	}
} 

B. Among Us

这道题我们考虑 \(k=8\),显然需要用到状压。

我们考虑有两个杀手,我们可以对每个杀手分别求一遍,然后合并起来。

对于一个杀手,我们设 \(f_{u,S}\) 表示当前杀手在 \(u\) 节点,杀了集合 \(S\) 的人需要的最短时间。

如何转移?我们考虑使用 \(dijstra\) 进行转移。

转移分成两种:

  • 待在一个房间干掉下一个人
  • 去其他房间

code:
还没有~~~

C. Magical Rearrangement

这道题显然从高位到低位,从小到大贪心即可。

当然需要特判只有一个 \(0\) 的情况(因为这个我 \(WA\) 了两发)

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 1e5 + 8;
int T;
int a[NN];

inline int read(){
	register char c = getchar();
	register int res = 0;
	while(!isdigit(c)) c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0',c = getchar();
	return res;
}

void solve(){
	int sum = 0;
	for(int i = 0; i <= 9; ++i) sum += (a[i] = read());
	if(sum == 1 && a[0] == 1){
		return puts("0"),void(0);
	}
	for(int i = 0; i <= 9; ++i){
		if((sum + (i != 0)) / 2 < a[i]) return puts("-1"),void(0);
	}
	for(int i = 1,pre = 0; i <= sum; ++i){
		int x = -1;
		for(int j = 0; j <= 9; ++j){
			if(pre == j) continue;
			if((sum - i + 2) == 2 * a[j]){x = j;break;}
		}
		if(x == -1){
			for(int j = 0; j <= 9; ++j){
				if(pre == j) continue;
				if(a[j] != 0){x = j;break;}
			}
		}
		printf("%d",x);
		--a[x];
		pre = x;
	}
	puts("");
}

int main(){
	T = read();
	while(T--){
		solve();
	}
}

D. Pattern Lock

我们考虑比较正常的想法就是我们可以发现以锯齿状的方式可以一次性将一个 \(2\times n\) 的图解决,如下图:

我们考虑怎么扩展到 \(n \times m(n \% 2 == 0)\),我们只需要将锯齿形改成下图即可:

可以发现上图都是小于 \(90^。\) 的。

现在我们就可以构造所有 \(n,m\) 中有一个是偶数的矩形,现在我们考虑如何构造 \(n,m\) 全是奇数的矩形:

我们发现,挖去一个 \(3\times 3\) 的矩形之后就会拆成两个边长一奇一偶的矩形,如下图:

然后上图中的浅绿色的线是拿来衔接矩阵之间的线,红色是锯齿形解决 偶\(\times\)奇 时上面破坏锯齿结构的横穿的线

至于左上角的 \(3\times 3\) 的矩形,可以构造一个自己喜欢的(其实解决边长有一个偶数的矩形也不止上面一种方法,读者可以自行探索)。

code:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int addx,addy;
bool tag;

void print(int x,int y){
	x += addx;y += addy;
	if(tag) printf("%d %d\n",y,x);
	else printf("%d %d\n",x,y);
}
void solve1(int n,int m,int op = 1){
	if(m == 2){
		for(int i = 1; i <= n; ++i){
			print(i,1);
			print(i,2);
		}
		return;
	}
	for(int i = 1; i <= n / 2; ++i){
		if((i & 1) == op){
			print(i*2-1,m);
			print(i*2,1);
			print(i*2-1,1);
			for(int j = 2; j <= m-1; ++j){
				print(i*2,j);
				print(i*2-1,j);
			}
			print(i*2,m);
		}
		else{
			print(i*2-1,1);
			print(i*2,m);
			print(i*2-1,m);
			for(int j = m-1; j >= 2; --j){
				print(i*2,j);
				print(i*2-1,j);
			}
			print(i*2,1);
		}
	}
}
void solve2(int n,int m){
	if(n > m) swap(n,m),tag ^= 1;
	if(((m-3) % 4) == 0){
		print(1,1);print(2,3);print(3,1);
		print(3,2);print(2,1);print(1,3);
		print(2,2);print(1,2);print(3,3);
		addx = 3;addy = 0;
		tag ^= 1;
		solve1(m-3,3,0);
		tag ^= 1;
		addx = addy = 0;
	}
	else{
		print(3,1);print(2,3);print(1,1);
		print(1,2);print(2,1);print(3,3);
		print(2,2);print(3,2);print(1,3);
		addx = 3;addy = 0;
		tag ^= 1;
		solve1(m-3,3,1);
		tag ^= 1;
		addx = addy = 0;
	}
	addx = 3,addy = 0;
	solve1(n-3,m,0);
}

int main(){
	scanf("%d%d",&n,&m);
	if(m % 2 == 0) swap(n,m),tag = 1;
	if(n % 2 == 0){
		solve1(n,m);
	}
	else{
		solve2(n,m);
	}
}

E. Stone Ocean

我们首先把期望转化为 权值和 \(/\) 总方案数。

这道题我们考虑还是状态压缩,设 \(f_{S}\) 表示将集合 \(S\) 中的字符串从外向内填入回文串的方案数。

因为当 \(|S| \bmod 2 == 0\) 时状态才有意义,可以证明状态数是 \(1.618^n\) 的。

至于代码实现可以使用队列将每一层可以到达的状态找出来,按层转移。

code:

还没有QAQ~~~

F. Jumping Monkey II

我们考虑,只有子树内的 \(LIS\) 是好求的,我们可以使用 启发式合并/线段树合并

我们考虑如何去求一个点父亲方向的 \(LIS\),考虑 淀粉质,我们可以将一个父亲方向的路径按分治中心分成 \(\log n\) 段。

然后对每个分治中心,维护其每棵子树的最长下降子序列的 DP 数组,将这些 DP 数组合并到分治中心,再贡献给每棵子树的每个结点

时间复杂度 \(O(n \log n)\)

code:

还没有QAQ~~~

G. Five Phases

我们考虑生成函数,由题可以得到答案是下面这个式子:

\[[v^aw^bx^cy^dz^e] \begin{pmatrix} v+w+x+y+z\\ +\frac 1 v + \frac 1 w+\frac 1 x+\frac 1 y+\frac 1 z\\ +vwx + wxy + xyz + yzv + zvw\\ +\frac 1 {vwx} + \frac 1 {wxy} + \frac 1 {xyz} + \frac 1 {yzv} + \frac 1 {zvw}\\ +\frac {vw} y + \frac {wx} z + \frac {xy} v + \frac {yz} w + \frac {zv} x\\ +\frac y {vw} + \frac z {wx} + \frac v {xy} + \frac w {yz} + \frac x {zv} \\ + \frac 1 {vwxyz}\\ + vwxyz \end{pmatrix}^k \]

然后我们考虑化简之后可以变成:

\[[v^aw^bx^cy^dz^e] \begin{bmatrix} \frac {(1+vw)(1+wx)(1+xy)(1+yz)(1+zv)} {vwxyz} \end{bmatrix}^k \]

然后我们设 \(vw,wx,xy,yz,zv\) 的次数为 \(o,p,q,r,s\),那么可以得到下面的方程组:

\[\begin{cases} a = s + o - k\\ b = o + p - k\\ c = p + q - k\\ d = q + r - k\\ e = r + s - k \end{cases} \Rightarrow \begin{cases} o = \frac {b-c+d-e+a+k} 2\\ p = \frac {c-d+e-a+b+k} 2\\ q = \frac {d-e+a-b+c+k} 2\\ r = \frac {e-a+b-c+d+k} 2\\ s = \frac {a-b+c-d+e+k} 2 \end{cases} \]

答案就是 \(\binom k o \binom k p \binom k q \binom k r \binom k s\)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 1e5 + 8,MOD = 998244353;

ll fac[NN],inv[NN];

inline int read(){
	register char c = getchar();
	register int res = 0,flag = 1;
	while(!isdigit(c)) flag = c == '-' ? -1 : flag,c = getchar();
	while(isdigit(c)) res = res * 10 + c - '0', c = getchar();
	return res*flag;
}
inline ll ksm(ll x,ll k){
	ll res = 1;
	while(k){
		if(k&1) res = res * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return res;
}
void init(){
	fac[0] = 1;
	for(int i = 1; i < NN; ++i) fac[i] = fac[i-1] * i % MOD;
	inv[NN-1] = ksm(fac[NN-1],MOD - 2);
	for(int i = NN-1; i >= 1; --i) inv[i-1] = inv[i] * i % MOD;
}
inline ll binom(ll n,ll m){
	if(m % 2 != 0) return 0;
	else m /= 2;
	if(m > n || n < 0 || m < 0) return 0;
	return fac[n] * inv[m] % MOD * inv[n-m] % MOD;
}
void solve(){
	ll a = read(),b = read(),c = read(),d = read(),e = read(),k = read();
	ll o,p,q,r,s;
	o = (b-c+d-e+a+k);
	p = (c-d+e-a+b+k);
	q = (d-e+a-b+c+k);
	r = (e-a+b-c+d+k);
	s = (a-b+c-d+e+k);
	printf("%lld\n",	binom(k,o)
					* 	binom(k,p) % MOD
					*	binom(k,q) % MOD
					*	binom(k,r) % MOD
					*	binom(k,s) % MOD);
}

int main(){
	init();
	int T = read();
	while(T--){
		solve();
	}
}

H. Reverse the String

你考虑字典序最小的字符串显然就是排序之后的字符串。

我们考虑翻转字符串的左端点应该在哪里,显然就是原字符串和排序之后的字符串的第一个不同的位置就是翻转的左端点。

我们现在需要找到最优的翻转的右端点,我们考虑 \(O(n^2)\) 做法就是对于每个右端点都跑一边,然后暴力找最优解。

我们考虑我们可以加速这个过程,我们考虑如何快速比较两个字符串的大小,显然可以通过 二分+Hash 解决这个问题。

code:

还没有QAQ~~~

I. Fake Walsh Transform

考虑这道题,我们发现 \(0\sim 2^m-1\) 的亦或和是 \(0\),那么我们只需要不亦或 \(n\) 即可。

当然当 \(n/m\)\(0/1\) 的时候有一些特殊情况需要特判一下。

J. Anti-merge

最后应该到达的状态显然是:

  • 对于任意单元格内容,将不添加标签看成 \(0\) 号标签。对于一个单元格,由单元格内容和单元格标签组成的 pair 对应该与它四连通的所有单元格互不相同。

考虑黑白棋盘染色,可知最少所用颜色数不超过 \(1\)

对于方案的求解,可以考虑使用 BFS,从每个位置开始对 pair 相同的单元格区域进行这种黑白染色,取黑白颜色数较少的颜色填充标签即可。

K. Longest Continuous 1

我们可以进行 打表 发现每个长度第一次出现的位置都是 \(111\dots111\) + \(100\dots000\) 的形式。

我们考虑如何证明:

我们发现连续的 \(1\) 只能由两个数字构成,因为两个数字一定是一奇一偶

  • 若前奇后偶,那么可以发现最早的一定是上面的情况
  • 若前偶后奇,那么最优一定会是这样:\(11\dots110111\) + \(11\dots111000\) 我们会发现它一定不能超过一个 \(01\) 串的长度,显然是不优的

我们考虑设 \(f(x)\) 表示 长度为 \(x\) 的连续的 \(1\) 第一次出现需要的长度,那么有如下递推式:

\[f(1) = 2\\ f(x) = f(x-1) + x(2^x - 1 - 2^{x-1} + 1) = f(x-1) + x(2^x-2^{x-1}) \]

然后我们就可以预处理出 \(f(1\sim 30)\),之后随便 爆扫 或者 二分答案 即可。

L. Tree Game

我们首先需要判断无解,这是很好判断的,就是忽略可以填任意数的位置,模拟一遍即可

然后我们设 \(f_{u}\) 表示以 \(u\) 为根的子树内正确排序的方案数,可以得到下面的转移方程:

\[f_u = (cnt0)! \prod f_v \]

\(cnt0\) 即为操作 \(u\)\(0\) 的个数。

为什么是 \((cnt0)!\) 呢?为什么不用确定哪个数是哪个呢?

我们考虑当最后的操作完之后,我们的每个 \(0\) 所在位置就是它应该填的数,所以我们只需要将 \(0\) 交换到应该的位置上。

因为前面已经判过了无解,所以说确定的数的位置是确定的,只有 \(0\) 的位置不确定,无论怎么填 \(0\) 的顺序在方案的合法性上都是等价的。

然后这道题就完了

最后这套题就完了~

有一些题目的代码有时间再补,没时间就咕咕咕~~~

posted @ 2024-01-10 21:05  ricky_lin  阅读(59)  评论(1编辑  收藏  举报