Atcoder Beginner Contest 313

Atcoder Beginner Contest 313

从C开始,由G结束(Ex的插头DP实在有点。

C

给定数组 a,每一次操作形如选择 1i,jnaiai+1,ajaj1。求使得最终 amaxamin1 的最小操作次数。

数学题。

关注不变量,在本题中,数列 a 的和始终不变,那么设 s=i=1nai。我们设最终情况下,数列 ax,x+1 组成,其中 x0m<n 个。

则可以得到方程:mx+(x+1)(nm)=s。稍加化简,可得 nx=s+mnn|(s+mn)n|(s+m)

因为 0m<n,所以 ss+m<s+n。因为 s+ns=n,所以 s+m=nx0m<n 的限制下有唯一解

可以枚举 m,判断是否有唯一解 x,m。此时问题得到了转化。

考虑到在最终局面下,原序列中所有比 x 小的数 ai 全部都会因增加变为 x,x+1。本着最优化原则,我们设 cnt=i=1n[aix],若 cntm,则答案为 i=1n[aix](xai),否则还需要花 cntm 次增加操作使得这些数变为 x+1,此时答案为 i=1m[aix](xai)+cntm

D

这是一道交互题。

有一个长度为 n 的数组 a,其中 i[1,n],0ai1

给定 k(kmod2=1),对于一次询问,给出 x1,x2xk,得到 i=1kaxkmod2 的值。

要求进行不超过 n 次询问,确定 a 数组的值。

我们记 f(x1xk) 为询问 x1xk 的答案。

则容易发现 f(x1xk1,i)=f(x1xk1,j)ai=aj,f(x1xk1,i)f(x1xk1,j)aiaj

一个自然的想法是,我们依次询问 [1,k],[2,k+1][n,k1](破环为链),就可以得到 (a1,ak),(a2,ak+1)(an,ak1)n1 对关系。

此时我们根据这 n1 对关系建立出一棵树,每条边形如:(i,j,[aiaj])

然后从 1 号点出发,作前缀异或和即可求出 a。根据 k 为奇数,我们统计 [1,k] 中与 a1 相等的数的个数 c。若 cmod2=f(1,2k),则说明 a1=1,否则 a1=0

但是,很遗憾,这是错的,并没有保证 nmodk0。因为 nmodk=0 时会出现环。

But,这思想值得借鉴。试问只要我们能够建立出一颗关系树即可求得答案。但是如何避免这个环呢?

我们可以用 [1,k1] 分别与 kn 构成询问,即可得到 k[k+1,n] 的关系,且无环。

接着,我们如何得到 [1,k1]k 的关系?可以借鉴原本的错误方法,我们询问 [2,k+1],[3,k+2][k,2k1]。再得 (1,k+1),(2,k+2)(k1,2k1)。这就必然不会出现环了。(想一想,为什么?)

思想:不仅可以确定值,也可以从关系上考虑。

    for(int i=1;i<=n;i++)a[i]=i;
	for(int i=n+1;i<=n+n;++i)a[i]=a[i-n];
	for(int i=k;i<=n;++i){
		cout<<"? ";
		for(int j=1;j<k;++j)cout<<j<<" ";cout<<i<<"\n";cout.flush();
		int x;cin>>x;
		if(i==k)s=x;
		else add(k,i,(s!=x));
	}
	int lst=s;
	for(int l=2;l<=k;++l){
		int x=ask(l);
		add(l-1,a[l+k-1],(x!=lst));
		lst=x;
	}
	for(int i=1;i<=n;i++)if(!vis[i])dfs(i,0);
	int cnt=0;
	for(int i=1;i<=k;i++)if(!dis[i])++cnt;
	cout<<"! ";
	if(s==(cnt&1)){
		for(int i=1;i<=n;i++)cout<<(dis[i]^1)<<" ";
	}
	else for(int i=1;i<=n;i++)cout<<dis[i]<<" ";

E

给定字符串 Si[1,|S|],0si9,定义 f(S) 为:

  • 新建一个字符串 T,最初为空。
  • 1|S|1 依次将 si 复制 si+1 次接在 T 后面。
  • f(S)=T

定义一次操作为 Sf(S),求经过多少次操作后 |S|=1,如果无解输出-1。

有意思的问题。我们先来考虑无解的情况

什么时候无解?那必定是每一次操作都会扩大 S 的长度。How?若存在 si>1,si+1>1,则 |f(S)||S|,且下一次仍然有符合条件的 (i,i+1)。综上,长度始终不减,无解。

故若有解,必然 i,si1si+1=1

现在我们来考虑计算操作数,由于从后面删,每删一个数又是一个同类子问题,考虑动态规划

fi 为删去 [i+1,n] 的最小次数。 fn+1=0,目标 f1

考虑当 si=1 时,进行一次操作足以删掉 si,故 fi=fi+1+1

si>1 时,由于原性质,必然有 si1=1,si+1=1,则在删去 [i+1,n] 之前,会复制 fi+1×(si1) 个 1出来(排除原来的)。

在删掉 [i+1,n] 之后,我们先执行一次操作删除 si,还要执行 (fi+1+1)(si1) 次操作删除末尾多生成的1。

所以 fi=(fi+1+1)(si1)+fi+1+1=si(fi+1+1)

发现两种情况下,递推式等价:fi=si(fi+1+1)。暴力算过去即可。

    cin>>n;cin>>a+1;
	for(int i=1;i<n;++i)
		if(a[i]!='1'&&a[i+1]!='1'){
			cout<<"-1\n";return 0;
		}
	for(int i=n;i>1;--i)ans=(a[i]-'0')*(ans+1)%p;
	cout<<ans<<"\n"

F

n(1n40) 张牌,每一张牌正面写上了数字 ai,背面写上了数字 bi。最初所有牌都是正面朝上。

m 个机器,每个机器有参数 xi,yi(1xi,yin)xi 可以等于 yi

每个机器只能启动一次,并且有 12 的概率将牌 xi 翻转,同时有 12 的概率将牌 yi 翻转。

你可以选择若干机器启动,使得最终局面中牌朝上的面的数字的期望和最大。求这个最大值。

有意思的期望问题。设选择的机器集合为 Q,我们先来算一下牌 i 被翻的概率 pi

cnti=kQ[xk=i]|[yk=i],则:

  1. cnti=0pi
  2. cnti>0pi=12

证明:

pi=k=0cnti2(c2k+1)2cnti=2cnti12cnti=12

综上,pi=12[cnti>0]

由此,我们可以得到一张牌是否被翻的概率与翻它的次数无关。

推论:每一次有 12 的概率将 x(x0,1) 异或 1 ,则最终被异或1的概率为 12

我们现在来考虑计算这个最大期望。显然,若翻了牌 i,则它的期望值为 ai+bi2,否则为 ai

设集合 Pcnti>0i 的集合,则期望:

E(Q)=xPax+xPax+bx2=xP2ax+xP(ax+bx)2=x=1n2axxP(axbx)2=i=1naixPaxbx2

s=i=1nai,w(P)=xPaxbx2,我们便要求 swmin

Trick:抽离常数项,直接算Δ 一般可以简化计算

然后,我们考虑计算答案,记 di=|aibi|2

先将牌分类S={di}(aibi),T={di}(ai<bi)

对于一台机器 i,若 xiT,yiT,显然就必选这台机器,此时我们将答案加上 dxi+dyi,然后将二者对应的 d 值改为0避免重复统计。

现在,我们来考虑计算贡献。

w(P)=xSPdxxTPdx

贪心地,我们要使得 xTPdx 尽量大。

这里用一个 启发式的思想

  1. |S|<|T|

显然,贪心地,若选择了 xSP,则与其相关的属于集合 T 的牌全部加入到 P 之中。

F(x) 表示在所有机器中,与 x 共享同一台机器的属于集合 T 的牌的集合,现在我们枚举集合 I=SP

根据贪心,PT=xIF(x)F(x) 可以 O(m) 预处理,枚举集合 I 的复杂度为 O(2|S|),计算贡献的复杂度为 n,所以复杂度为 O(m+n2|S|)|S|n2

  1. |S|>|T|

这时候也是一个关于子集的最优化问题。考虑使用动态规划求解。

这里的性质是什么呢?和上一个条件一样,若选择了 xS,则 F(x) 中的所有数都应该被选择。

所以,我们设 dpi,A 表示在前 i1 张属于 S 的牌中,选出属于集合 T 的牌的集合为 A 时,最小的代价和(这里的代价定义为从 S 中选出的牌的 d 值的和的相反数)。

容易得到:

dpi+1,Amin(dpi+1,A,dpi,A)

dpi+1,AF(Si)min(dpi+1,AF(Si),dpi,AdSi)

DP复杂度为 O(|S|2|T|)

然后考虑计算答案:我们枚举 A,则

wmin=minAT{iAdi+dp|S|+1,A}

复杂度 O(|T|2|T|)。所以总复杂度 O(m+|S|2|T|+|T|2|T|)=O(m+n2|T|)

Trick:启发式分类处理.

在实现上,为了方便处理,可以先把所有的 a,b 值乘2,最后来除2即可。

#include<iostream>
#include<vector>
#include<cstring>
#define N 505050
#define int long long
using namespace std;
int a[N],b[N],x[N],y[N],id[N],fz[N],f[45][1<<21],ans;
vector<int>s,t;
signed main(){
	ios::sync_with_stdio(false);int n,m;cin>>n>>m;
	for(int i=0;i<n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=m;i++)cin>>x[i]>>y[i],x[i]--,y[i]--;
	for(int i=1;i<=m;i++)if(x[i]==y[i]&&a[x[i]]<b[y[i]])swap(a[x[i]],b[y[i]]);
	for(int i=0;i<n;i++){
		a[i]<<=1,b[i]<<=1;ans+=a[i];
		if(a[i]>=b[i])id[i]=s.size(),s.push_back((a[i]-b[i])/2);
		else id[i]=t.size(),t.push_back((b[i]-a[i])/2);
	} 
	for(int i=1;i<=m;i++){
		int l=x[i],r=y[i];
		int f=(a[l]>=b[l]),g=(a[r]>=b[r]);
		if(f==g&&f==0){
			ans+=t[id[l]]+t[id[r]];
			t[id[l]]=t[id[r]]=0;
		}
		else if(f!=g){
			if(!f)swap(l,r);
			fz[id[l]]|=(1ll<<id[r]);
		}
	}
	int cs=s.size(),ct=t.size(),mx=0;
	if(cs<=ct){//\sum t-s
		for(int i=0;i<(1ll<<cs);++i){
			int w=0,c=0;
			for(int j=0;j<cs;++j){
				if((i>>j)&1)w-=s[j],c|=fz[j];
			}
			for(int j=0;j<ct;++j)if((c>>j)&1)w+=t[j];
			mx=max(mx,w);
		}
	}
	else {
		memset(f,-0x3f,sizeof f);
		f[0][0]=0;
		for(int i=0;i<cs;++i){
			for(int j=0;j<(1ll<<ct);++j){
				f[i+1][j]=max(f[i+1][j],f[i][j]);
				f[i+1][j|fz[i]]=max(f[i+1][j|fz[i]],f[i][j]-s[i]);
			}
		}
		for(int i=0;i<(1<<ct);++i){
			int w=f[cs][i];
			for(int j=0;j<ct;++j)if((i>>j)&1)w+=t[j];
			mx=max(mx,w);
		}
	}
	ans+=mx;
	if(ans&1)cout<<ans/2<<".500000\n";
	else cout<<ans/2<<".000000\n"; 
}

G

n 堆石子,石子数相异,每次操作分为以下两种:

  • 从每一个还有石子的堆中各取出一个放入背包。
  • 从背包中取出 n 个石子放入每一堆中。

可以进行无限次操作,求可能形成的局面个数。(对 998244353 取模)

ABC313简直了。

显然对于一个局面来说,操作顺序没有影响,我们只需要考虑两种操作分别进行的次数即可。

不妨假设先全部进行操作1,再全部进行操作2。

显然,清空的堆数不同,局面数必定不同。(清空 k 堆,最终局面下 1k 堆石子相同且必定小于 ak+1)

h(k) 为清空 k 堆石子的最终局面数,则我们设进行了 i 次操作1,显然 i[ak,ak+11]

设前缀和数组 s,则此时袋子里有 (nk)i+sk 个石子,总共可以进行 (nk)i+skn+1 次操作。

则:

h(k)=i=akak+11(nk)i+sk+nn=i=ak+1ak+1(nk)i+sk+kn

对于这个式子的计算?看类欧几里得算法简单形式

特别地,h(n)=snn+1

答案为 i=1nh(i)。为啥没 h(0)?是因为 h(0)h(1) 中被统计了。

int f(int a,int b,int c,int n){
	if(n<0)return 0;
	int w=0;
	if(a>=c)w+=n*(n+1)*(a/c)/2,a%=c,w%=p;
	if(b>=c)w+=(n+1)*(b/c),b%=c,w%=p;
	int m=(a*n+b)/c;
	w+=n*m%p-f(c,c-b-1,a,m-1)%p;w%=p;
	return w;
} 
signed main(){
	read(n);for(int i=1;i<=n;i++)read(a[i]);
	sort(a+1,a+n+1);s=0;int ans=0; 
	for(int i=1;i<n;i++){
		s+=a[i];
		ans=(ans+f(n-i,s+i,n,a[i+1])-f(n-i,s+i,n,a[i]))%p;
		ans=(ans%p+p)%p;
	}
	s+=a[n];ans+=s/n+1;
	cout<<(ans%p+p)%p<<"\n";
}
posted @   spdarkle  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示