Codeforces Round 893 Div.2 A~E2

Codeforces Round 893 Div.2 A~E2

CF1858A

a+b+c 个球。
其中的 a 个只能被先手取,b 个只能被后手取,c 个双方都可以取。

双方轮流取球,每次取走一个,无法取球者输。

T 组询问,每次给定 a,b,c 问哪一方必胜。

考虑到只需要关心谁先不能取即可。

显然能取必定是先取那 c 个。直接令 a=a+c2,b=b+c2。比谁大谁小即可。

CF1858B

你背着一个有无穷多饼干的书包去逛街。

街上从左到右一共有 n 个摊位和 m 个商人,第 i 个商人在第 si 个摊位卖饼干。

你从最左边开始,向右依次走过第 1,2,,n1,n 个摊位,从一个摊位走到和它相邻的摊位需要 1 分钟。

每当走过第 i 个摊位时,你会依照这样的条件吃饼干:

  • 如果这个摊位上有一个商人,则从商人那里购买一个饼干并立即吃掉。
  • 否则如果你还没有吃过饼干,则从书包里拿出一个饼干并立即吃掉。
  • 否则如果你前一次吃饼干到现在已经过了 d 分钟,也就是你在第 max(id+1,1)i1 个摊位都没有吃饼干,则从书包里拿出一个饼干并立即吃掉。

注意这些条件不会同时满足,亦即你在同一个摊位不会吃掉超过 1 个饼干。

如今人员拥挤,管理部门需要移除恰好一个商人。

你想知道怎样移除一个商人使你能吃到的饼干最少。

请输出移除一个商人后你能吃到的最少饼干数量,以及有多少种移除方案使你能吃的饼干最少。

2dn1092mn1sinsi 单调递增,m105

注意到原则: 简化边界判断

为了满足在第一个位置吃东西,可以建立 s0=1d,顺带为了方便最后一个位置判断,建立 sm+1=n+1

然后设 wi=sisi11d,这是两个位置之间的贡献。

则在不删人的情况下, ans=m+i=1m+1wi

那么考虑删掉 i,则 Δwi=wi1+wi+1si+1si11d

显然,Δwi1。所以满足条件的个数为: i=1mΔwi,ans=ans1

注意若这个个数为0,则满足条件的个数为 m,ans=ans

signed main(){
	int t=read();
	while(t--){
		int m,d;k=0;n=read(),m=read(),d=read();s[++k]=-d+1;
		for(int i=1;i<=m;i++)s[++k]=read();int tag=0,ans=0;
		if(d==1){
			cout<<n<<" "<<m<<"\n";continue;
		}
		s[++k]=n+1;
		for(int i=1;i<k;i++)w[i]=(s[i+1]-s[i]-1)/d,ans+=w[i];ans+=m; 
		for(int i=2;i<k;i++){
			if((s[i+1]-s[i-1]-1)/d<w[i]+w[i-1]+1)++tag;
		}
		if(tag)cout<<ans-1ll<<" "<<tag<<"\n";
		else cout<<ans<<" "<<m<<"\n"; 
	}
}
 

CF1858C

要求构造一个 n 的排列,编号为 0n1,使得 gcd(ai,a(i+1)modn) 最大。

n=5: 1 2 4 3 5
n=7: 1 2 3 6 4 5 7
n=10: 1 2 3 4 8 5 10 6 9 7

观察样例,不难发现令 bi=gcd(ai,a(i+1)modn),则最大时 b 中会出现 2n2 各一个,其余全是1.

那么我们倍增去构造这些值即可。这是必定可以构造出来的。(想一想,为什么)

void get(int x){
	if(v[x]||x>n)return ;
	b[++num]=x,v[x]=1;
	get(x<<1);
}
//main
		cin>>n;num=0;
		for(int i=1;i<=n;i++){
			get(i);
		}
		for(int i=1;i<=n;i++)if(!v[i])b[++num]=i;
		for(int i=1;i<=n;i++)v[i]=0;
		for(int i=1;i<=n;i++)cout<<b[i]<<" ";
		cout<<"\n";

CF1858D

给出一个01串,可以进行不超过 k 次将一个位取反的操作。

l0 为更改后最长的0段长度,设 l1 为更改后最长的1段长度。

对于每一个 a[1,n],求 al0+l1 的最大值。

n3000

简单问题。其实拆一下变成 l0+l1+(a1)l0。我们不妨枚举 l0 的长度,然后求当前状态下 l1 的最大值即可。

不妨设 fx 表示在最长0段长 x 时,最长1段的最大长度。

ansa=maxxa+fx

考虑求出 f,怎么搞。其实也是容易的。我们可以枚举 x,再枚举这个0段的区间左右端点 l,r(rl+1=x)

可以计算出在 [l,r] 内需要更改的次数 c,然后问题化为:在 [1,l1],[r+1,n] 中,使用不超过 kc 次修改,使得1段最长的长度。

这两边是不同的两个同类子问题,可以通过动态规划求得答案。

prei,j 表示在 [1,i] 中,最多修改 j 次,最长1段长度,设 sufi,j 表示在 [i,n] 中,最多修改 j 次,最长1段长度。

则分为两种情况:最长 1 段的末尾是否是 i

对于不是 i 的情况,有 prei,j=prei1,j,sufi,j=sufi+1,j

对于是 i 的情况,可以向前/后扫一遍,设扫到 j,扫到 c 个0,则 prei,c=sufi,c=|ij|+1

两种情况取较大值即可。

最后本要做一个后缀最小值,但做不做无所谓了,等价的。

		cin>>n>>m;
		for(int i=1;i<=n;i++){
			char x;cin>>x;a[i]=x-'0';
		}
		for(int i=0;i<=n;i++)f[i]=ans[i]=-inf;
		for(int i=0;i<=n+1;i++)for(int j=0;j<=n;j++)pre[i][j]=suf[i][j]=0;
		for(int i=1;i<=n;i++){
			int c=0;
			for(int j=0;j<=n;j++)pre[i][j]=pre[i-1][j];
			for(int j=i;j;--j){
				c+=(!a[j]);
				pre[i][c]=max(pre[i][c],i-j+1);
			}
		}
		f[0]=pre[n][m];
		for(int i=n;i;--i){
			int c=0;
			for(int j=0;j<=n;j++)suf[i][j]=suf[i+1][j];
			for(int j=i;j<=n;j++){
				c+=(!a[j]);
				suf[i][c]=max(suf[i][c],j-i+1);
			}
		}
		for(int i=1;i<=n;i++){
			int c=0;
			for(int j=i;j<=n;j++){
				c+=a[j];if(c>m)break;
				f[j-i+1]=max(f[j-i+1],max(pre[i-1][m-c],suf[j+1][m-c]));
			}
		}
		for(int i=1;i<=n;i++)for(int j=0;j<=n;j++)ans[i]=max(ans[i],i*j+f[j]);
		for(int i=1;i<=n;i++)cout<<ans[i]<<" ";cout<<"\n"; 

CF1858E2

维护一个初始为空的序列,支持以下操作:

  • x:在序列末端插入 x
  • k:在序列末端删除 k 个数(k 不超过当前序列长度);
  • ?:查询序列中不同的数字个数;
  • !:撤回前一个 +/- 操作。

q 次操作,强制在线,1q,x106,询问操作不超过 105

操作问题,可以用操作树来考虑,显然真正有意义的操作只是插入。

我们考虑记录节点 x,表示当前序列为操作树上 Path(1,x)。先分析一下各个操作。

则对于操作1,直接新开一个儿子 y,并将 x 转移到 y 即可。ansy=ansx+[iPath(1,x),aiay]

对于操作2,直接将 x 变为其树上 k 级祖先即可。

对于操作4,我们可以维护上一次进行1/2操作的指针位置,并回跳后更新当前操作即可 prei=preprei1

对于询问答案,直接在插入时就维护 ans 即可。

考虑维护。

可以这样抽象一颗操作树,将儿子分为两类。第一类是表示操作1出来的,第二类是表示其他操作的。

其他操作的点是没有子树的。

然后对于2,3,4的更新,直接找到前一个对应位置,将其在树上所对一类点作为自己的父亲,顺带转移信息。

我们只需要考虑操作1,2怎么实现。

操作2很简单,做一个树上 k 级祖先的倍增即可。

操作1?我们可以使用主席树维护所有的一类点。

时间复杂度 O(nlogn),但空间也是 O(nlogn),只有256MB,被卡了。

这里我们就可以注意到,在主席计算时,无需记录原本区间的左右端点,边传边算。

同时注意我们只是查一个数值是否为1,所以只需要看能否走到对应位置(是否存在这个点)即可。

总的来说,只需要记录动态开点的左右儿子即可。

空间255MB,emmmm。

维护四个东西:

  • lst:上一个有效操作所对位置
  • ver:实际指向的一类点位置
  • s:目前答案
  • f:倍增 k 级祖先

对于几个操作状态的维护,就直接看代码了,这里不详细叙述了。

#include<iostream>
#include<cstdio>
using namespace std;
int cnt;
struct node {
	int lc, rc;
}t[21000005];
void update(int &s,int L,int R,int pos,int dis) {
	s=++cnt;int mid=L+R>>1;
	t[s]=t[pos];
	if (L==dis&&R==dis)return;
	if (dis>=L&&dis<=mid)update(t[s].lc,L,mid,t[pos].lc,dis);
	else update(t[s].rc,mid+1,R,t[pos].rc,dis);
}
int find(int pos,int L,int R, int dis) {
	int s=pos;
	if(!s)return 0;
	if(L==R)return 1;int mid=L+R>>1;
	if(dis<=mid)return find(t[s].lc,L,mid, dis);
	else return find(t[s].rc,mid+1,R, dis);
}
int id,lst[1050000],len,c;
int f[1000005][20],s[1050000],ver[1050000];
void add(int x,int pos){ 
	lst[pos]=pos-1;
	ver[pos]=ver[pos-1];
	s[pos]=s[pos-1];f[pos][0]=pos-1;
	for(int i=1;i<20;i++)f[pos][i]=f[f[pos][i-1]][i-1];
	if(find(ver[pos],1,1e6,x))return ;
	update(ver[pos],1,1e6,ver[pos],x);
	s[pos]++;
}
void del(int k,int pos){
	lst[pos]=pos-1;
	int id=pos-1;
	for(int i=0;i<20;i++)if((k>>i)&1)id=f[id][i];
	ver[pos]=ver[id],s[pos]=s[id];
	for(int i=0;i<20;++i)f[pos][i]=f[id][i];
}
void move(int pos){
	int id=lst[pos-1];
	lst[pos]=lst[id];ver[pos]=ver[id],s[pos]=s[id];
	for(int i=0;i<20;i++)f[pos][i]=f[id][i];	
}
int solve(int pos){
	lst[pos]=lst[pos-1],ver[pos]=ver[pos-1],s[pos]=s[pos-1];
	for(int i=0;i<20;i++)f[pos][i]=f[pos-1][i];
	return s[pos];
}
char o;
int main() {
	int t,pos=0;
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--){
		cin>>o;++pos;
		if(o=='+'){
			int k;cin>>k;
			add(k,pos);
		}
		else if(o=='-'){
			int k;cin>>k;
			del(k,pos);
		}
		else if(o=='!'){
			move(pos);
		}
		else {cout<<solve(pos)<<"\n";cout.flush();}
	}
	return 0;
}//codeforces
posted @   spdarkle  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示