CF1034赛后总结

文章目录

A

题面:

翻译:

水题一道,上来就直接场切了。大致思路就是配对,像 0 0 0 要找 3 3 3 1 1 1 要找 2 2 2 这样,把满足条件的放一堆,然后你就会发现每一堆的数量就是 4 4 4 个(这也很好证明,就拿来当课后习题了),所以就是看 n n n 能不能被 4 4 4 整除,能就是 Bob 胜,反之就是 Alice。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		cout<<(n%4?"Alice":"Bob")<<endl;//我甚至连 if 都懒得写……
	}
	return 0;
}

B

题面:

翻译:

看完第一排:似乎很普通……

看完第四排:我的天!概期!

看完题面:普通的模拟……

很显然:当 k > 1 k\gt1 k>1 时,不管是谁都有概率活下来,因为它可以和旁边的几个联合起来,谁也不打谁,然后让其他人互相厮杀(虽然有点苟,但至少苟下来了)。

现在就来看 k = 1 k=1 k=1 时。同样很显然:最后只有最大的那个活了下来(具体原因不需要我过多解释了吧)。

因此就先特判一下,然后看看是不是最大值就完了。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,x,k,a[200006];
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>x>>k;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		if(k>1)
		{
			cout<<"YES"<<endl;
			continue;
		}
		int mx=0;
		for(int i=1;i<=n;i++)
		{
			mx=max(mx,a[i]);
		}
		cout<<(a[x]==mx?"YES":"NO")<<endl;
	}
	return 0;
}

水题一道,实在不想喷……

C

题面:

翻译:

水题一道。(结果因为没初始化被卡了八次……)

首先很显然的一件事:第一个数和最后一个数一定能存活下来,因为第一个数后面的那一串可以用后缀消掉一堆,只留下最大的那个,然后第一个数再通过前缀或者是后缀消掉这个数,于是它就存活下来了,最后一个数同理。

现在我们来看中间的数。首先要明确一个点:某个数前面的数不管用多少次前缀都等于用一次前缀,后面的数不管用多少次后缀都等于一次后缀(这个很好想,我就不过多赘述了)。什么意思呢?比如说 1 3 4 2 6 7 5 这一组数,现在我们看那个数字 2,它的前面的数可以用两次前缀操作:[1 3] 4 2 6 7 5 → \to [1 4] 2 6 7 5 → \to 1 2 6 7 5,也可以只用一次:[1 3 4] 2 6 7 5 → \to 1 2 6 7 5。但不管用了多少次,它们的最终结果都是一样的,后缀同理。

所以我们实际上只需要考虑这个数前面的数通过前缀操作得到的数、这个数本身、这个数后面的数通过后缀操作得到的数,然后看这个数能否通过前缀和后缀操作再把左右的两个数消掉就行了。(条件自己想,想不通的可以看代码。)

然后这道题就完美的 A C \color{green}{AC} AC 了!

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,a[200006],mn[200006],mx[200006],b[200006];
signed main()
{
	memset(mn,0x3f,sizeof(mn));
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			mn[i]=min(mn[i-1],a[i]);
		}
		for(int i=n;i>=1;i--)
		{
			mx[i]=max(mx[i+1],a[i]);
		}
		b[1]=b[n]=1;
		for(int i=1;i<=n;i++)
		{
			if(a[i]<mn[i-1]&&a[i]<mx[i+1])//如果我是最小的,直接用前缀操作
			{
				b[i]=1;
			}
			if(a[i]>mn[i-1]&&a[i]>mx[i+1])//如果我是最大的,直接用后缀操作
			{
				b[i]=1;
			}
			if(a[i]<mn[i-1]&&a[i]>mx[i+1])//如果我比前面小、比后面大,先用前缀,再用后缀
			{
				b[i]=1;
			}
		}
		for(int i=1;i<=n;i++)
		{
			cout<<b[i];
			mx[i]=b[i]=0;//初始化初始化初始化!
			mn[i]=0x3f3f3f3f3f;
		}
		cout<<endl;
	}
	return 0;
}

D

题面:

翻译:

水题,但是被样例坑了……

首先这题很好想的就是爱丽丝的最优策略,对于爱丽丝来讲,最好的方案一定是这种:

1 00 … 0 ⏟ k − 1 个 0 1 1\underbrace{00\dots0}_{k-1\text{个}0}1 1k10 0001

这样的话不论鲍勃填哪儿, 1 1 1 的数量都会少一个。

但是如果鲍勃填完了中间所有的 0 0 0,那么就会出现 k + 1 k+1 k+1 1 1 1,爱丽丝还是无法取胜。因此,我们需要稍微改变一下策略:

00 … 0 ⏟ k − 1 个 0 1 00 … 0 ⏟ k − 1 个 0 \underbrace{00\dots0}_{k-1\text{个}0}1\underbrace{00\dots0}_{k-1\text{个}0} k10 0001k10 000

这样,不管鲍勃填哪儿,最终都只会剩下 k k k 1 1 1,然后爱丽丝就可以一招制敌了。

通过上面策略,我们可以轻松地得出一个不等式:

2 ∣ n ≤ k − 1 < k 2\mid n\le k-1\lt k 2nk1<k

其中 ∣ \mid 表示整除。

再这个条件下,爱丽丝一定能取胜。

然后这题就做完了……

特殊情况:当原本的 1 1 1 的个数小于等于 k k k 的个数时,爱丽丝就可以直接胜利。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,k;
string s;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>k;
		cin>>s;
		int sum=0;
		for(int i=0;i<n;i++)
		{
			sum+=s[i]-'0';
		}
		if(sum<=k)//就是这里,样例里面的 1 的个数是不小于 k 的,结果……
		{
			cout<<"Alice"<<endl;
			continue;
		}
		cout<<(n/2>=k?"Bob":"Alice")<<endl;
	}
	return 0;
}

E

题面:

翻译:

考试时没做出来,看完题解才发现自己好傻……

正向思维:枚举 k k k,然后针对每一个 k k k 求一次 MEX ⁡ \operatorname{MEX} MEX 的数量(太难了没写出来)。

逆向思维:确定一个 MEX ⁡ \operatorname{MEX} MEX,然后看可以让这个 MEX ⁡ \operatorname{MEX} MEX 满足的 k k k 有哪些。

我们假定 a x a_x ax 是当前我们认定的 MEX ⁡ \operatorname{MEX} MEX,那么我们要让它成立,最简单的方法就是把 a x a_x ax 全删了,最复杂的方法就是不仅把 a x a_x ax 全删了,比 a x a_x ax 小的数也全只保留一个。那么在这个区间里的 k k k 值就都能让 MEX ⁡ = a x \operatorname{MEX}=a_x MEX=ax 这个条件成立。然后用差分数组统计一下数量,最后用前缀和求出答案。

注意:如果当前的 a x a_x ax 的个数是 0 0 0 的话,剩下的比它大的数就都不需要统计了,因为 MEX ⁡ \operatorname{MEX} MEX 永远不可能取到它们。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n;
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int>num(n+6,0);
		for(int i=1,x;i<=n;i++)
		{
			cin>>x;
			num[x]++;
		}
		vector<int>c(n+6,0);
		for(int i=0;i<=n;i++)
		{
			int l=num[i],r=n-i;
			c[l]++,c[r+1]--;//差分数组
			if(!num[i])//注意:第一个个数为 0 的数是原数组的 MEX,是成立的,因此这个数也要统计进去
			{
				break;
			}
		}
		for(int i=1;i<=n;i++)
		{
			c[i]+=c[i-1];
		}
		for(int i=0;i<=n;i++)
		{
			cout<<c[i]<<" \n"[i==n];
		}
	}
	return 0;
}

F

题面:

翻译:

考试时没做出来,写个题解纪念一下。

主要思路:找出所有的质数,然后把质数的倍数循环交换(如果已经被其他质数交换过就不需要了),最后直接输出答案即可。(这是我见过的思路最简短的 F 题……)

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n;
bool is_prime(int x)
{
	for(int i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			return false;
		}
	}
	return true;
}
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int>prime;
		for(int i=2;i<=n;i++)
		{
			if(is_prime(i))
			{
				prime.emplace_back(i);
			}
		}
		reverse(prime.begin(),prime.end());
		//注意:质数要从小到大
		//因为小质数有更多的倍数来交换
		//但大质数就没有那么多倍数了
		vector<int>vis(n+6,0);
		vis[1]=1;
		for(auto i:prime)
		{
			int last=0;
			for(int j=i;j<=n;j+=i)
			{
				if(vis[j])
				{
					continue;
				}
				vis[j]=last;
				last=j;
			}
			vis[i]=last;
		}
		for(int i=1;i<=n;i++)
		{
			cout<<vis[i]<<" \n"[i==n];
		}
	}
}

G

题面:

翻译:

没想出来怎么做,但有一个时间复杂度较高的做法。在此我说一下我的思路,以起到抛砖引玉的效果。

我主要是想到了 a i a_i ai 不断加上 k k k 然后再对 m m m 取余,实际上就是让 a i a_i ai 再一个同余集里面循环跳动,而这个同余集的余数就是 a i   m o d   gcd ⁡ ( k , m ) a_i\bmod\operatorname{gcd}(k,m) aimodgcd(k,m),而题目中要我判断 a a a 能否为非递减序列,所以我就可以不断调整每个位置上的数(也就是不断加上 gcd ⁡ ( k , m ) \operatorname{gcd}(k,m) gcd(k,m),因为加上最大公因数后原数的余数不变),看看能不能找到一种方案使得条件成立,然后我又通过一些奇怪的方法把时间复杂度压到了 O ( n q ) O(nq) O(nq),然后我就不会了,希望评论区有哪个大佬能帮忙解惑一下。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,m,q,a[100006],b[100006];
signed main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>m>>q;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		while(q--)
		{
			int x,y;
			cin>>x>>y;
			if(x==1)
			{
				int z;
				cin>>z;
				a[y]=z;
			}
			else
			{
				int d=__gcd(m,y),cnt=1;
				for(int i=1;i<=n;i++)
				{
					b[i]=a[i]%d;
					if(b[i]<b[i-1])
					{
						cnt++;
					}
				}
				cout<<(cnt<=m/d?"YES":"NO")<<endl;
			}
		}
	}
	return 0;
}

总结:

  • A至C题:完美切掉(注意初始化问题)。
  • D题:被样例坑了,下次不光要看样例,还要仔细审题。
  • E、F题:思维上欠缺,还需多练。
  • G题:完全不会……题解也没看懂。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值