Codeforces Round #685 (Div. 2) 题解

前言

比赛链接

打这场的时候状态巨差emmm

A. Subtract or Divide

题目链接

题意

\(t\) 组数据,每组给个正整数 \(n\) ,每次操作可以把当前的数字除以它的一个非本身的因数或者把它减一,问把 \(n\) 变成 \(1\) 的最少操作次数
\(1 \le t \le 1000,1 \le n \le 10^9\)

题解

对于 \(n \le 3\) ,就是直接减到 \(1\) ,操作数 \(n - 1\)
对于其余的偶数 \(n\),则将 \(n\) 除以 \((n/2) = 2\) ,然后再减 \(1\),操作数 \(2\)
对于其余奇数 \(n\),将 \(n\)\(1\) 成为偶数,再进行偶数的操作,操作数 \(3\)
很容易证明这样做操作数最少

时间复杂度: \(O(t)\)

#include<bits/stdc++.h>
int t,n;
int main()
{
	for(scanf("%d",&t);t--;) {
		scanf("%d",&n);
		if(n<=3) printf("%d\n",n-1);
		else if(n%2&1) puts("3");
		else puts("2");
	}
}

B. Non-Substring Subsequence

题目链接

题意

\(t\) 组数据,每组给定一个长度为 \(n\) 的字符串 \(s\)\(q\) 个询问,每个询问给定 \(l, r\) 问是否存在一个 \(s\)非连续子序列 \(a\) 使得 \(a\) 等于 \(s_l s_{l+1} \cdots s_r\) 这个子串
(非连续的子序列即子序列中至少有一对相邻的字符在原串的位置不相邻)
\(1 \le t,q \le 100, 2 \le n \le 100, 1 \le l_i < \le r_i \le n\)

题解

对每组询问的处理 \(s\) 的每个前缀处理询问串的最长前缀的长度使得询问串的那个前缀是该前缀的子序列;
对每个后缀同样处理;
枚举断点 \(i\) ,如果至少有一个 \(i\) 使得前缀 \(i-1\) 能包含的长度 \(+\) 后缀 \(i+1\) 能包含的长度 \(\ge\) 询问串长度即有解,否则无解;
\(O(t \cdot q \cdot n)\)

#include<bits/stdc++.h>
#define MN 105
int t,n,q,pre[MN],suf;
char s[MN];
int main()
{
	for(scanf("%d",&t);t--;) {
		scanf("%d%d%s",&n,&q,s+1);
		for(int i=1;i<=q;i++) {
			bool ok=0;
			int l,r;
			scanf("%d%d",&l,&r);
			memset(pre,0,sizeof pre);
			suf=0;
			for(int j=1;j<=n;j++) {
				pre[j]=pre[j-1];
				if(pre[j]==(r-l+1)) continue;
				if(s[j]==s[l+pre[j]]) pre[j]++;
//				printf("%d %d\n",j,pre[j]);
			}
			for(int j=n;j;j--) {
//				printf("%d %d %d\n",j,pre[j-1],suf);
				if(suf+pre[j-1]>=(r-l+1)&&suf&&pre[j-1]) {
					ok=1;
					break;
				}
				if(s[j]==s[r-suf]) suf++;
			}
			if(ok) puts("YES");
			else puts("NO");
		}
	}
}

C. String Equality

题目链接

题意

\(t\) 组数据,每组给定 \(k\) 和长度为 \(n\) 的字符串 \(a\)\(b\);你可以将 \(a\) 的相邻两位交换,或者将 \(a\) 中连续 \(k\) 位相同的字符全部 \(+ 1\) (即 \(a\) 变为 \(b\), \(b\) 变为 \(c\),以此类推)
问进行若干次操作是否能将 \(a\) 变为 \(b\)
\(1 \le t \le 10^5, 2 \le n \le 10^6, 1 \le k \le n, \sum n \le 10^6\)

题解

\(f_i\) 表示需要将多少个字符 \(i-1\) 转成字符 \(i\)\(num_{a,i}, num_{b,i}\) 表示字符串 \(a\)\(b\) 中字符 \(i\) 的个数;
显然 \(f_i = num_{b,i} - num_{a,i} + f_{i+1}\), 边界 \(f_z = num_{b,i} - num_{a,i}\)
无解 \(\Leftrightarrow \exists_i (f_i < 0) \vee (f_i \mod{k} \ne 0)\)

#include<bits/stdc++.h>
#define MN 1000005
int t,n,k,sa,sb,nb[27],na[27];
char a[MN],b[MN];
int main()
{
	for(scanf("%d",&t);t--;) {
		memset(na,0,sizeof na);
		memset(nb,0,sizeof nb);
		scanf("%d%d%s%s",&n,&k,a+1,b+1);
		sa=sb=0;
		for(int i=1;i<=n;i++) {
//			printf("%d %d\n",a[i]-'a',b[i]-'a');
			na[a[i]-'a']++;
			nb[b[i]-'a']++;
		}
//		if(sa>sb||(sb-sa)%k!=0) {
//			puts("NO");
//			continue;
//		}
		int nd=0;
		bool ok=1;
		for(int i=25;i>=0;i--) {
			nd+=nb[i];
			if(na[i]>nd) {
				ok=0;
				break;
			}
			nd-=na[i];
//			printf("%d %d %d %d\n",i,nd,na[i],nb[i]);
			if(nd%k!=0) {
				ok=0;
				break;
			}
		}
		puts(ok?"yes":"no");
	}
}

D. Circle Game

题目链接

题意

在平面直角坐标系中,初始点在 \((0,0)\),两人轮流操作,每次操作可以将横坐标或纵坐标加 \(k\),且要保证操作后到 \((0,0)\) 的平面距离不超过 \(d\)\(x^2 + y^2 \le d^2\)),问先手必胜还是后手必胜。

题解

我比赛时的做法好捞。。。两份题解都写下吧

官方题解

结论:如果设 \(z\) 为使 \((kz,kz)\)\((0,0)\) 的距离不超过 \(d\) 的最大整数,若 \((k(z+1),kz)\) 的距离也不超过 \(d\),则先手必胜,否则后手必胜。

  • 如果 \((k(z+1),kz)\) 的距离超过 \(d\)
    则显然无论先手如何操作,后手一定都能让其到 \(y=x\) 这条直线上(如果先手往右走,后手就往上走;先手往上,后手就往右);
    当到达 \((kz,kz)\) 时先手就没操作可走了。
  • 如果 \((k(z+1),kz)\) 的距离不超过 \(d\)
    先手先往右走一步,接下来无论如何操作,先手都一定有办法让点在 \(y=x-k\) 这条直线上;
    所以最后会让点到 \((k(z+1),kz)\) 且后手操作;
    又因为 \((k(z+1),k(z+1))\) 的距离大于 \(d\)\((k(z+2),kz)\) 的距离 \(= k^2(2z^2+4z+4) > dis_{(k(z+1),k(z+1))} = k^2(2z^2+4z+2) > d^2\),所以此时无法操作。
    复杂度 \(O(t)\)
#include<bits/stdc++.h>
int main()
{
	int t,d,k;
	for(scanf("%d",&t);t--;) {
		scanf("%d%d",&d,&k);
		int z=floor(sqrt((long double)d*d/2/k/k));
		if(1ll*k*k*(z+1)*(z+1)+1ll*k*k*z*z<=1ll*d*d) puts("Ashish");
		else puts("Utkarsh");
	}
}
//好简单啊淦

我的捞捞做法

将平面上能走到的点想象成网格图,那么此时相当于可以在网格图上向上或者右走,无法走的时候就输了
且每一行能走的列数不会比下一行少(设第 \(i\) 行能走的列数为 \(h_i\)
不妨设 \(0\) 表示先手必胜, \(1\) 表示后手必胜
显然对于第 \(h_{[sqrt(d^2)]}\) 行也就是最后一行,它从最右边开始分别是 $0,1,0,1 \cdots $ 这样交错的
那么对于第 \(h_i\) 行,如果它等于 \(h_{i+1}\) 那么它的状态就是上一行所有位置取反
如果不相等,就要对于 \(j, h_{i+1} < j < h_i\) 的那些列,也和初始一样,是 \(0,1\) 交错的
然后如果把 \(h_{i+1}\) 整个取反后正好和右边的部分还是 \(0,1\) 交错的,那就直接拼;
否则如果拼完后是 \(1,1\) 我们会发现它每过一行就会往左一格(将它周围的格子状态推出后就能证明),判断这个 \(1,1\) 会不会是使起始点变成先手必胜;
如果是 \(0,0\) ,那么过一行就又会变成 \(1,1\) 跟上面做法相同
判断下起始点是 \(1\) 还是 \(0\) 就好了
实在是捞的很
复杂度 \(O(t\times d)\)

#include<bits/stdc++.h>
typedef long long ll;
ll t,d,k;
bool fu[100005];
int main()
{
	scanf("%lld",&t);
	for(;t--;) {
		memset(fu,0,sizeof fu);
		bool ok=1;
		scanf("%lld%lld",&d,&k);
		ll dc=0,uc=0;
		for(ll x=(d/k*k),ly=0;x>=0;x-=k) {
			ll y=floor(sqrt(d*d-x*x));
			y=y/k+1;
//			printf("%lld %lld %lld\n",x,y,uc);
			if(ly==0){dc=(y%2==0)?x:x+k;ly=y;continue;}
			if(y>ly&&(((y-ly)%2==0)^(uc==1))) {
//				printf("%lld %lld\n",x-(uc==0)*k-ly*k+k+k,x-(uc==0)*k-ly*k+k);
					if(x-(uc==0)*k-ly*k+k+k>=0) fu[x-(uc==0)*k-ly*k+k+k]=1;
					if(x-(uc==0)*k-ly*k+k>=0) fu[x-(uc==0)*k-ly*k+k]=1;
			}
			if(y==ly) uc^=1;
			else uc=0;
			ly=y;
			if(fu[x]) dc=x;
		}
		puts(((dc/k+1)&1)?"Ashish":"Utkarsh");
	}
}

E1/2. Bitwise Queries

题目链接E1/题目链接E2

题意

交互题,有 \(n\) 个值域在 \([0,n)\) 间的数,每次询问你可以问其中两个数按位与/或/异或的值,你需要答出这 \(n\) 个数是多少
\(n\) 保证是 \(2\) 的次幂, \(n \ge 4\)
E1 : 最多进行 \(n+2\) 次询问; E2 : \(n+1\) 次询问

题解

\(a \oplus b = a + b - 2 \& b\) (先假设 \(a\)\(b\) 每一位二进制都不同时为 \(1\) ,然后每一位同时为 \(1\) 的二进制位都会多算 \(2 \times\) 该二进制位的值)

那对于 E1 就直接问出 \(3\) 个数的两两的异或值和按位与的值(\(b \oplus c\) 的异或值不用问,直接 \(b \oplus c= (a \oplus b) \oplus (a \oplus c)\) 算出来就好了,然后加下变成两两相加,就能算出这 \(3\) 个数的值了,操作数 \(5\)
其余的就直接问他们与这 \(3\) 个数其中一个的异或值,操作数 \(n-3\)
总操作数 \(n+2\)

对于 E2 ,先询问 \(a_2 \cdots a_n\)\(a_1\) 的异或值,如果有两个数的查询的值相同,则这两个数相等(或者如果某个数查询的值等于 \(0\) 就跟 \(a_1\) 相等)
那么这两个数就等于他们的按位与值,算出值然后就能算出所有数的值了,操作数 \(n\)
如果没有数相同,那么整个数组恰好就是 \(0\)\(n-1\) 的一个排列,其中必然有个数是 \(a_1\) 关于全集(\(n-1\))的补集,设其为 \(a_x\),则 \(a_x \oplus a_1\) 一定等于 \(n-1\) ,且 \(a_x \& a_1 = 0\)
那再找个数 \(a_z\) ,问 \(a_x \& a_z\)\(a_1 \& a_z\),按 E1 的做法就做完了,操作数 \(n+1\)

#include<bits/stdc++.h>
int n,a[1<<16|5],A,B,C,b[1<<16|5];
int query(int i,int j,int ty) {
	if(ty==0) printf("AND");
	else if(ty==1) printf("OR");
	else printf("XOR");
	printf(" %d %d\n",i,j);
	fflush(stdout);
	int v;
	scanf("%d",&v);
	return v;
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++) a[i]=query(1,i,2);
	b[0]=1;
	int x=-1,y,z;
	for(int i=2;i<=n;i++) {
		if(b[a[i]]) {
			x=b[a[i]];
			y=i;
			break;
		}
		b[a[i]]=i;
	}
	if(x!=-1) {
		a[1]=a[y]^query(x,y,0);
	} else {
		x=1;y=b[n-1];
		if(y==n) z=n-1;
		else z=n;
		A=a[z]+2*query(z,x,0);
		B=(a[z]^a[y])+2*query(z,y,0);
		C=a[y];
		int yyy=A+C-B;
		yyy>>=1;
		a[1]=yyy;
	}
	for(int i=2;i<=n;i++) a[i]^=a[1];
	printf("!");
	for(int i=1;i<=n;i++) printf(" %d",a[i]);puts("");
}

F. Nullify The Matrix

题目链接F

题意

还是博弈论,\(t\) 组数据,每组给个 \(r \times c\) 的矩阵,矩阵上的点有值,每次操作可以选择一个不为 \(0\) 的点作为起点,任意一个在起点的右上方的点作为终点(可以同行,同列,或者与起点同点,即终点 \((X_e, Y_e)\) 要满足当起点是 \((X_b, Y_b)\) 时,\(X_b \le X_e \le r, Y_b \le Y_e \le c\)),再选择一个起点到终点的最短路径(即只能往右或者往上走),然后对于这条路径,起点必须减去一个不为 \(0\) 的数,其余点可以任意修改为一个非负整数(可以增加,不变,也可以减小),且要满足操作后所有点仍然非负,问先手必胜还是后手必胜。
可以证明在最优策略下可以通过有限次操作结束游戏。
\(1 \le t \le 10, 1 \le n,m \le 100, 0 \le a_{i,j} \le 10^6\)

题解

分析这个操作方式。
发现对于选择的路径上的点,每个点的横坐标与纵坐标之和都是上一个的 \(+1\),所以显然如果当满足 \(x + y < a\) 的点已经全为 \(0\) 时,对于 \(x + y = a\) 的那些点只能被选为起点(而不能被选为路径上的其它点),那么他们在每次操作后都可以变小而无法变大,所以最优策略下是有限操作的

分析到这里已经很明显了:答案一定跟这些对角线(即 \(x+y\) 相等的线)有关; 事实也是如此:
当所有对角线的异或和都为 \(0\) ,先手必败,否则先手必胜

  • 如果当前满足所有对角线的异或和为 \(0\)
    如果整个矩阵都为 \(0\),则先手必败;
    否则假设先手选择的起点 \((x_0, y_0)\) 在直线 \(x + y = a\) 上,先手进行操作使得直线 \(x + y = a\) 上所有点的异或和变为 \(b\)
    为使起点的值要减少, \(b \oplus v_{(x_0,y_0)} < v_{(x_0,y_0)}\),也就是假设 \(b\) 的二进制最高位为 \(i\)\(v_{(x_0,y_0)}\) 的二进制表示下第 \(i\) 位也必须为 \(1\)
    那么由于操作前所有点异或和为 \(0\) ,一定可以找到一个点满足它的二进制表示第 \(i\) 位也为 \(1\) ,后手选其为起点,把他的值改为 \(v \oplus b\),就能保持 \(x + y = a\) 这条线上的异或和为 \(0\)
    对于先手改变的 \(x + y > a\) 的那些点,后手可以任意改变,所以仍可以保持他们异或和为 \(0\)
    通过这样的操作会让 \(x+y\) 比较小的线上的值往后移,所以此时先手必败。
  • 如果存在一条线使得它异或和不为 \(0\)
    类似地,我们设 \(a\) 为最小的整数满足所有 \(x + y = a\) 的点异或和不为 \(0\) ($ = b$,假设 \(b\) 二进制表示下最高位为 \(i\));
    我们一定可以找到一个 \(x + y = a\) 的点使得它二进制下第 \(i\) 位为 \(1\),选其为起点;
    对于 \(x + y > a\) 的线由于其可以任意改变,则一定可以改变成满足异或和为 \(0\) 的情况;
    所以此时一定可以转移到上面那种情况也就是先手必败态,所以此时先手必胜。
    复杂度 \(O(t * nm)\)
#include<bits/stdc++.h>
#define MN 105
int n,m,t;
int b[MN<<1];
int main()
{
	for(scanf("%d",&t);t--;) {
		scanf("%d%d",&n,&m);
		memset(b,0,sizeof b);
		for(int i=1;i<=n;i++) for(int j=1,x;j<=m;j++) scanf("%d",&x),b[i+j]^=x;
		int ok=0;
		for(int i=1;i<=n+m;i++) ok|=b[i];
		puts(ok?"Ashish":"Jeel");
	}
}
posted @ 2020-11-25 20:39  zzpcd  阅读(121)  评论(0编辑  收藏  举报