Codeforces Round #670 (Div. 2) 题解

本文解析了五道经典算法题目,包括贪心算法、数论、树形结构、序列拆分和质数判断等,提供了详细的思路分析和C++代码实现。

A

简单的贪心,就直接上代码了:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 110

int T,n,a[maxn],b[maxn];

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);
		memset(b,0,sizeof(b));
		for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[a[i]]++;
		int ans=0,now=0;
		while(b[now]>1)ans+=2,now++;
		while(b[now]>0)ans++,now++;
		printf("%d\n",ans);
	}
}

B

mi[i],ma[i]mi[i],ma[i]mi[i],ma[i] 表示枚举到当前位置,用 iii 个数能够乘出的最小和最大数,他们的转移只和 mi[i−1],ma[i−1]mi[i-1],ma[i-1]mi[i1],ma[i1] 相关,瞎搞搞就好了。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define ll long long

int T,n;
ll a[maxn],mi[10],ma[10];

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);ll ans=-1e18;
		for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
		for(int i=1;i<=5;i++)mi[i]=1e18,ma[i]=-1e18;
		for(int i=1;i<=n;i++){
			for(int j=5;j>=2;j--)if(i>=j){
				mi[j]=min(mi[j],min(mi[j-1]*a[i],ma[j-1]*a[i]));
				ma[j]=max(ma[j],max(mi[j-1]*a[i],ma[j-1]*a[i]));
			}
			mi[1]=min(mi[1],a[i]);
			ma[1]=max(ma[1],a[i]);
			ans=max(ans,ma[5]);
		}
		printf("%lld\n",ans);
	}
}

C

看一下一开始几个重心,如果有两个,那么把其中一个重心的一个叶子给另一个重心,这样重心就唯一了。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010
#define pb push_back

int T,n;
vector<int>e[maxn];
int size[maxn],mson[maxn],rt,rt_;
void dfs(int x,int fa){
	size[x]=1;mson[x]=0;
	for(int y:e[x])if(y!=fa){
		dfs(y,x);size[x]+=size[y];
		if(size[y]>mson[x])mson[x]=size[y];
	}
	if(n-size[x]>mson[x])mson[x]=n-size[x];
	if(mson[x]<mson[rt])rt=x,rt_=0;
	else if(mson[x]==mson[rt])rt_=x;
}
void dfs1(int x,int fa,int &l,int &f){
	int son=e[x].size()-1;
	if(!son)l=x,f=fa;
	else if(e[x][0]!=fa)dfs1(e[x][0],x,l,f);
	else dfs1(e[x][1],x,l,f);
}

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)e[i].clear();
		for(int i=1,x,y;i<n;i++)scanf("%d %d",&x,&y),e[x].pb(y),e[y].pb(x);
		mson[rt=rt_=0]=1e9;dfs(1,0);
		if(rt_){
			int l,f;
			dfs1(rt,rt_,l,f);
			printf("%d %d\n%d %d\n",l,f,l,rt_);
		}else{
			printf("%d %d\n%d %d\n",1,e[1][0],1,e[1][0]);
		}
	}
}

D

先考虑如何将一个序列拆成一个不下降和一个不上升的序列。考虑只做减法,让这个序列变成不上升序列,那么就是要将每一个极长不下降子段整体减小,直到最大值等于上一个子段的最小值。

那么每个位置减去的值,一定能够形成一个不下降子序列,两个序列就构造完了。然后还要使他们的最大值最小,令 a,ba,ba,b 为两个序列当前的最大值,显然可以让大的那个整体减小,小的整体增加,使得最大值为 ⌈a+b2⌉\lceil \frac {a+b} 2\rceil2a+b

不上升子序列的最大值显然就是第一位上的数,而不下降子序列最大值是该序列最后一位上的数,令原序列做差分得到 bbb 数组,那么最后一位上的数就是所有满足 b[i]>0b[i]>0b[i]>0b[i]b[i]b[i] 之和。

修改的话直接修改差分数组,可以 O(1)O(1)O(1) 完成。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long long

int n,m;
ll a[maxn],b[maxn],sum=0;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),b[i]=a[i]-a[i-1];
	for(int i=2;i<=n;i++)if(b[i]>0)sum+=b[i];
	printf("%lld\n",(ll)ceil(1.0*(b[1]+sum)/2.0));
	scanf("%d",&m);
	for(int i=1,l,r,x;i<=m;i++){
		scanf("%d %d %d",&l,&r,&x);
		if(l>1&&b[l]>0)sum-=b[l];
		b[l]+=x;
		if(l>1&&b[l]>0)sum+=b[l];
		if(r<n){r++;
			if(b[r]>0)sum-=b[r];
			b[r]-=x;
			if(b[r]>0)sum+=b[r];
		}
		printf("%lld\n",(ll)ceil(1.0*(b[1]+sum)/2.0));
	}
}

E

发现 10510^5105 以内的质数有 959295929592 个,这意味着每个质数大约只能操作 111 次。

先枚举 n\sqrt nn 以内的质数,将 xxx 质因数分解后这些质数的出现次数可能超过 111,每次暴力 BBB 一下枚举到的 ppp,然后枚举 p,p2,p3,...p,p^2,p^3,...p,p2,p3,... 依次做 AAA 操作,这样可以确定他们的出现次数。

再考虑大于 n\sqrt nn 的质数,这些质数最多出现一次,并且此时在 n\sqrt nn ~ nnn 范围内已经没有合数了(除了 xxx)。

先考虑 xxx 是大于 n\sqrt nn 的质数,每次 BBB 一半的数,如果 BBB 完之后询问 A 1A~1A 1 和预期的不一样,那么这之中一定有 xxx,每个 AAA 一下就能找到,否则就在另外一半里面,重复上面的操作,可以发现这样操作需要的次数大概就是质数个数次的。

再考虑 xxx 拥有大于 n\sqrt nn 的质数作为因子,但不是质数的情况,那么在上面 BBB 的过程中,看看是否某一次 BBB 出了 222 就好了。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,ans=1;
int prime[maxn],t=0;
bool v[maxn];
void work(){
	for(int i=2;i<=n;i++){
		if(!v[i])prime[++t]=i;
		for(int j=1;j<=t&&i*prime[j]<=n;j++){
			v[i*prime[j]]=true;
			if(i%prime[j]==0)break;
		}
	}
}
int ask(char x,int y){
	printf("%c %d\n",x,y);fflush(stdout);
	if(x!='C'){int re;scanf("%d",&re);return re;}
	else exit(0);
}
int st=1,sum;
void solve(int l,int r){
	int mid=l+r>>1;
	for(int i=l;i<=mid;i++){
		if(ask('B',prime[i])>1)ask('C',ans*prime[i]);
		sum--;
	}
	if(ask('A',1)!=sum){
		for(int i=l;i<=mid;i++)
		if(ask('A',prime[i]))ask('C',ans*prime[i]);
	}
	if(mid<r)solve(mid+1,r);
}

int main()
{
	scanf("%d",&n);work();sum=n;
	for(;st<=t&&prime[st]*prime[st]<=n;st++){
		int &x=prime[st];
		sum-=ask('B',x);
		if(ask('A',1)!=sum){
			sum++;
			for(int j=x;j<=n&&ask('A',j);j*=x)ans*=x;
		}
	}
	if(st<=t)solve(st,t);
	ask('C',ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值