Codeforces Round #649 解题报告

Codeforces round 649 赛后解题报告

A. XXXXX

这个题是 1h 50min 才做出来的

首先我们先来关注一句话:

An array a a a is a subarray of an array b b b if a can be obtained from b b b by deletion of several (possibly, zero or all) elements from the beginning and several (possibly, zero or all) elements from the end.

注意 subarray。所以是一段连续的子序列啊啊啊啊啊。

所以我们现在的思路就很明确了。

1.如果 ∀ i ≤ n , x ∣ a i \forall i\leq n,x\mid a_i in,xai,答案肯定为 − 1 -1 1
2.如果 x ∤ ∑ i = 1 n a i x\nmid \sum_{i=1}^n a_i xi=1nai,我们 直接输出 n n n 即可。
3.问题现在集中在如果不属于前两种情况,即 x ∤ ∑ i = 1 n a i x\nmid \sum_{i=1}^n a_i xi=1nai,且不是所有的 a i a_i ai 都是 x x x 的倍数。这种情况我们可以从前往后扫一遍,直到出现不是 x x x 的倍数的数就可以停下来,从后往前进行相同的操作,最后比较一下那种更优即可。

为什么?我们知道对于两个 x x x 的倍数 a , b a,b a,b。我们设 a = p ⋅ x , b = q ⋅ x a=p\cdot x,b=q\cdot x a=px,b=qx a − b = ( p − q ) ⋅ x a-b=(p-q)\cdot x ab=(pq)x,也是 x x x 的倍数,所以我们要找到一个不能被 x x x 整除的数才可以。**我们最后要在“从前往后”和“从后往前”得出的答案中选一个。 原因很简单,自己想想就可以知道的对吧。那为啥我没想到呢/kk。

#include<bits/stdc++.h>
#define int long long
using namespace std;
 
int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}
 
const int maxn=1e5+10;
 
int n,a[maxn],cnt[maxn],x;
 
signed main() {
	int t;
	t=read();
	while(t--) {
		fill(cnt,cnt+n+1,0);
		n=read();x=read();
		int sum=0;
		
		for(int i=1;i<=n;i++) {
			a[i]=read();
			cnt[a[i]%x]++;
			sum+=a[i]%x;
		}
		if(cnt[0]==n) {
			cout<<-1<<endl;
			continue;
		}
		else if(sum%x!=0) {
			cout<<n<<endl;
			continue;
		}
		
		int left=1,right=n;
		
		for(;left<=n;left++) {
			if(a[left]%x!=0) break;
		}
		for(;right>=1;right--) {
			if(a[right]%x!=0) {
				break;
			}
		}
		cout<<max(n-left,right-1)<<endl; 
	}
	return 0;
}

B. Most socially-distanced subsequence

这个题比 A 简单

我们先来看一个单调递增的区间: a 1 , a 2 , a 3 . . . a k − 1 , a k a_1,a_2,a_3...a_{k-1},a_k a1,a2,a3...ak1,ak,满足 a 1 < a 2 < a 3 < . . . < a k − 1 < a k a_1<a_2<a_3<...<a_{k-1}<a_k a1<a2<a3<...<ak1<ak

对于这个区间,如果我们选择了 a b 1 , a b 2 , a b 3 . . . a b q − 1 , a b q a_{b_1},a_{b_2},a_{b_3}...a_{b_{q-1}},a_{b_q} ab1,ab2,ab3...abq1,abq,其中 b 1 < b 2 < b 3 < . . . < b q − 1 < b q ≤ n b_1<b_2<b_3<...<b_{q-1}<b_q\leq n b1<b2<b3<...<bq1<bqn,此时我们得到的答案为
a n s = ( ( a b 2 − a b 1 ) + ( a b 3 − a b 2 ) + . . . + ( a b q − a b q − 1 ) ) ans=((a_{b_2}-a_{b_1})+(a_{b_3}-a_{b_2})+...+(a_{b_q}-a_{b_{q-1}})) ans=((ab2ab1)+(ab3ab2)+...+(abqabq1))
a n s = a b q − a b 1 ans=a_{b_q}-a_{b_1} ans=abqab1

由此可得:
a n s m a x = a k − a 1 ans_{max}=a_k-a_1 ansmax=aka1

单调递减的区间同理。

因此我们只需要找出每一个单调递增和单调递减的区间即可,看代码。

//#pragma GCC optimize("Ofast","-funroll-loops","-fdelete-null-pointer-checks")
//#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}

const int maxn=1e5+10;

int n,a[maxn],sum[maxn],x;

signed main() {
	int t;
	t=read();
	while(t--) {
		n=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}

		int ans=0,last=1,len=2;//last记录分界点
		bool now=a[1]<a[2];//now记录递增还是递减,为1就是递增,0是递减,下同

		for(int i=2;i<=n;i++) {
			if(a[i]<a[i-1]==now) {
				ans+=abs(a[i-1]-a[last]);
				last=i-1;
				now=1-now;//递增递减交换
				len++;
			}
		}

		ans+=abs(a[n]-a[last]);
		cout<<len<<endl; 

		now=a[1]<a[2];

		cout<<a[1]<<" ";
		for(int i=2;i<=n;i++) {
			if(a[i]<a[i-1]==now) {
				printf("%lld ",a[i-1]);
				now=1-now;//递增递减交换
			}
		}
		cout<<a[n]<<endl;//注意一头一尾单独处理~
	}
	return 0;
}

C. Ehab and Prefix MEXs

这个题比 A 简单

这个题有一个很重要的条件

It’s guaranteed that a i ≤ a i + 1 a_i\leq a_i+1 aiai+1 for 1 ≤ i < n 1\leq i<n 1i<n.

所以我们可以进行这样的处理,先找出所有的没出现的数字。如果 a i = a i − 1 a_i=a_{i-1} ai=ai1。那么我们为了尽量让题目有解,一定要尽量把更大的数放进答案数组 b b b 里。如果 a i ≠ a i − 1 a_i\neq a_{i-1} ai=ai1。我们就要把 a i − 1 a_{i-1} ai1 放到 b i b_i bi 上,因为这个数字不再是最小未出现过的数了。其实这个题还是非常简单的。

什么时候无解呢?我们现在极限的来操作一下 b b b 数组

i=123456i
b=012345i-1
a=123456i

我们发现 a i a_i ai 的最大值为 i i i,所以如果输入时 a i > i a_i>i ai>i,输出 − 1 -1 1 即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}

const int maxn=1e6+10;

int n,a[maxn],b[maxn],maxx[maxn];
bool ex[maxn];
vector<int> v;//存放未出现的数字

signed main() {
	n=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
		ex[a[i]]=1;
		if(a[i]!=0&&a[i]>i) {//注意 a_i!=0
			cout<<-1<<endl;
			return 0;
		}
	}
	for(int i=0;i<=1e6;i++) {
		if(!ex[i]){
			v.push_back(i);
		}
	}
	int index=0;//记录当前放到哪个数
	
	for(int i=1;i<=n;i++) {
		if(i==1||a[i]==a[i-1]) {
			b[i]=v[index++];
		}
		else {
			b[i]=a[i-1];
		}
		
		printf("%lld ",b[i]);
	}
	return 0;
}

D - Ehab’s Last Corollary

这个题是一道很好的“图论+树”的题。

推荐一道同一个人出的类似的题:Ehab’s Last Theorem

题目有云:

I have a proof that for any input you can always solve at least one of these problems, but it’s left as an exercise for the reader.

我们就来先证明一下。

我们要思考一个问题,什么时候我们能找到一个大小为 ⌈ k 2 ⌉ \lceil \frac{k}{2}\rceil 2k 的独立集?只有当我们能在图中找到一个大小为 k k k 的树或一个大小为 k k k 的简单环时才可以,然而此时我们可以回答第一个问题,我们也就证明了一定有解。

简单环就是任意环上不相邻的点没有连线,即 ( v i , v i + 1 ) ∉ E (v_i,v_{i+1})\notin E (vi,vi+1)/E

我们提供两种解法:

法一

首先我们来解决 n = k n=k n=k 的情况。这个时候如果图是一棵树,那么我们一定能找到独立集这个问题的解,否则就可回答第一个问题。

如果 n ≠ k n\neq k n=k,我们就找到一个大小为 k k k 的联通子图,用 n = k n=k n=k 的方法去做,问题就迎刃而解了。

那么我们来讲一下如何找到一个环(不限大小)。

我们先来看一个图:

F

首先,我们随便以一个点作为起点进行 DFS。我们以1位起点。那么,我把DFS中经过过的边(也就是DFS树),边权为 1 1 1,其他为 0 0 0。那么原图就变成了这个样子:

F2

我们发现,由 1 1 1 边构成的图是一棵树,虽然这个性质对我们的解题毫无用处,但这个DFS树真的是没什么用,了解就好,他对我们的思路启发是有一点作用的。我们继续讲题,现在,如果在DFS时,我们从 u u u 出发,找到了一个点 v v v,如果之前 v v v 已经被搜到过了,那么我们就找到了一个环。这个环的大小就至少为 d f n u − d f n v + 1 dfn_u-dfn_v+1 dfnudfnv+1。( d f n dfn dfn 表第 i i i 个点被遍历到的时间戳)。

#include<bits/stdc++.h>
using namespace std;

int read() {
    char ch=getchar();
    int f=1,x=0;
    while(ch<'0'||ch>'9') {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return f*x;
}

const int maxn=1e5+10;

struct edge {
	int v,next;
}e[maxn<<2];

int n,h[maxn],h2[maxn],cnt,m,k,cnt2;
bool ex[maxn],flag,is[maxn];
stack<int> s;
vector<int> ans[2];

void addedge(int u,int v) {
	e[++cnt].v=v;
	e[cnt].next=h[u];
	h[u]=cnt;
}
void insert(int u,int v) {
	addedge(u,v);
	addedge(v,u);
}

void dfs(int u,int fa) {//找环
	s.push(u);
	ex[u]=1;
	for(int i=h[u];i;i=e[i].next) {
		int v=e[i].v;
		
		if(fa!=v&&is[v]) {//is[v]判断答我们遍历的点是否在我们建的新图里
			if(ex[v]) {//找到就输出
				int sz=s.size();
				
				for(int j=0;j<sz;j++) {
					ans[0].push_back(s.top());
					if(s.top()==v) {
						break;
					}
					s.pop();
				}
				
				sz=ans[0].size();
				printf("2\n%d\n",sz);
				for(int j=0;j<sz;j++) {
					printf("%d ",ans[0][j]);
				}
				exit(0);
			}
			
			dfs(v,u);
		}
	}
	s.pop();
	ex[u]=0;
}

void color(int u,int fa,int type) {//找独立集
	ans[type].push_back(u);
	for(int i=h[u];i;i=e[i].next) {
		int v=e[i].v;
		
		if(fa!=v&&is[v]) {
			color(v,u,1-type);
		}
	}
}

void create(int u,int fa,int num) {//建新图
	is[u]=1;
	for(int i=h[u];i;i=e[i].next) {
		int v=e[i].v;
		
		if(!is[v]) {
			if(flag) {
				return ;
			}
			if(num==1) {
				flag=1; 
			}
			create(v,u,num-1);
		}
	}
}

int main() {
	n=read();m=read();k=read();
	
	for(int i=1;i<=m;i++) {
		int a=read(),b=read();
		insert(a,b);
	}
	
	m=0;
	create(1,0,k-1);
	
	dfs(1,0);
	
	printf("1\n");
	color(1,0,0);
	
	if(ans[0].size()<ans[1].size()) {
		swap(ans[0],ans[1]);
	}
	int sz=ans[0].size();
	sz=min(sz,(k+1)/2);//判断一下我们找到的最大独立集的大小是否一定小于 ceil(k/2)
	for(int i=0;i<sz;i++) {
		printf("%d ",ans[0][i]);
	}
	
	return 0;
}

法二

我们先来找一个简单环,如果这个环的大小大于 k k k,染色法找独立集,否则直接输出。

E. X-OR

这个题是一道交互题。(蒟蒻第一次做交互题,所以一直不理解规则,于是。。。。)

在这里插入图片描述

我们来具体讲讲怎么做。

首先我们先了解一下什么叫“按位或”。“按位或”就是对于两个数的每一个二进制位上进行布尔运算。每一位 1 , 0 1,0 1,0 的结果如下表。

10
111
010

我们再来了解一些“按位或”的性质。

性质1

max ⁡ ( x , y ) ≤ x ∣ y \max(x,y)\leq x|y max(x,y)xy

证明1

因为 x , y x,y x,y 每一位上进行了“或”的操作后得到的结果,总归是大于等于这两个数的这一位二进制数。得证。

性质2

0 ∣ x = x 0|x=x 0x=x

证明2

0 0 0 的每一位二进制上都是 0 0 0,所以答案就等于 x x x

知道这些后,我们就可以来做题啦。解法有点像模拟退火的思路?

我们发现 4269 4269 4269 这个数很有意思,我们又有 3 ≤ n ≤ 2048 3\leq n\leq 2048 3n2048。我们就会发现 4269 = 2048 × 2 + 173 4269=2048\times 2+173 4269=2048×2+173一开始还以为应该是 4096 4096 4096,出题人打错了。。。。

所以我们可以考虑通过两次 n n n 次的询问,和一些小问题的处理。所以我们可以非常容易的相处一个方法。我们先用 n + k ( k ≤ 173 ) n+k(k\leq 173) n+k(k173) 次找出 0 0 0 的位置,在通过 n − 1 n-1 n1 次询问就可以知道 P P P。问题就转化为如何在限定次数内找出 0 0 0 的位置。

首先我们随机找两个位置记为 x , y x,y x,y 作为 0 0 0 的可能所在的位置,记 v a l = P x ∣ P y val=P_x|P_y val=PxPy。我们随机一个位置 z ( z ≠ x , z ≠ y ) z(z\neq x,z\neq y) z(z=x,z=y)

1.如果 P x ∣ P y > P y ∣ P z P_x|P_y>P_y|P_z PxPy>PyPz,那么代表 P x ≠ 0 P_x\neq 0 Px=0。可由性质1得出,我们用 z z z 代替 x x x
2.如果 P x ∣ P y < P y ∣ P z P_x|P_y<P_y|P_z PxPy<PyPz,还则罢了。
3.如果 P x ∣ P y = P y ∣ P z P_x|P_y=P_y|P_z PxPy=PyPz P y P_y Py 一定不是 0 0 0,用 z z z 代替 y y y

现在我们找到了两个可能为 0 0 0 的位 x , y x,y x,y。我们在他们之间只需要找出那个为 0 0 0 的位置即可。我们只需要用跟上面相同的方法,还是随机出一个 z z z,同样 z ≠ x , z ≠ y z\neq x,z\neq y z=x,z=y

1.如果 P x ∣ P z > P y ∣ P z P_x|P_z>P_y|P_z PxPz>PyPz,那么 P x = 0 P_x=0 Px=0
2.如果 P x ∣ P z < P y ∣ P z P_x|P_z<P_y|P_z PxPz<PyPz,那么 P y = 0 P_y=0 Py=0

最后只需在询问一遍所有的位置便可得出答案。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int read() {
	char ch=getchar();
	int f=1,x=0;
	while(ch<'0'||ch>'9') {
		if(ch=='-')
			f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=x*10+ch-'0';
		ch=getchar();
	}
	return f*x;
}

const int maxn=2100;

int n,tool[maxn],ans[maxn];

int PLA(int i1,int i2) {//Please Let me know the Answer
	cout<<"?"<<" "<<i1<<" "<<i2<<endl;
	cout.flush();//注意基本格式
	return read();
}


signed main() {
	srand(71179);//别乱想,没啥特殊意义(jiade)
	
	n=read();
	
	for(int i=1;i<=n;i++) {
		tool[i]=i;
	}

	random_shuffle(tool+1,tool+n+1);
	random_shuffle(tool+1,tool+n+1);//越随机越好
	random_shuffle(tool+1,tool+n+1);
	
	int x=tool[1],y=tool[2];
	int v=PLA(x,y);
	
	for(int i=3;i<=n;i++) {
		int z=tool[i];
		if(z==y||z==x) {
			continue;
		}
		int tv=PLA(z,y);
		
		if(tv<v) {
			v=tv,x=z;
		}
		else if(tv==v) {
			y=z;
			v=PLA(x,y);
		}
	}
	
	while(1) {
		int z=tool[rand()%n+1];
		if(z==x||z==y) continue;
		
		int v1=PLA(x,z),v2=PLA(y,z);
		
		if(v1==v2){
			continue;
		}
		if(v1>v2) {
			swap(x,y);
		}
		
		for(int i=1;i<=n;i++) {
			if(i!=x) {
				ans[i]=PLA(i,x);
			}
		}
		
		cout<<"!";
		for(int i=1;i<=n;i++) {
			cout<<" "<<ans[i];
		}
		cout.flush();//注意基本格式
		return 0;
	}

	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值