Atcoder Beginner Contest 313

Atcoder Beginner Contest 313

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

C

给定数组 \(a\),每一次操作形如选择 \(1\le i,j\le n\)\(a_i\leftarrow a_i+1,a_j\leftarrow a_j-1\)。求使得最终 \(a_{\max}-a_{\min} \le 1\) 的最小操作次数。

数学题。

关注不变量,在本题中,数列 \(a\) 的和始终不变,那么设 \(s=\sum_{i=1}^na_i\)。我们设最终情况下,数列 \(a\)\(x,x+1\) 组成,其中 \(x\)\(0\le m< n\) 个。

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

因为 \(0\le m<n\),所以 \(s\le s+m<s+n\)。因为 \(s+n-s=n\),所以 \(s+m=nx\)\(0\le m< n\) 的限制下有唯一解

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

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

D

这是一道交互题。

有一个长度为 \(n\) 的数组 \(a\),其中 \(\forall i\in[1,n],0\le a_i\le 1\)

给定 \(k(k\bmod 2=1)\),对于一次询问,给出 \(x_1,x_2\dots x_k\),得到 \(\sum_{i=1}^ka_{x_k} \bmod 2\) 的值。

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

我们记 \(f(x_1\dots x_k)\) 为询问 \(x_1\sim x_k\) 的答案。

则容易发现 \(f(x_1\dots x_{k-1},i)=f(x_1\dots x_{k-1},j)\implies a_i=a_j,f(x_1\dots x_{k-1},i)\neq f(x_1\dots x_{k-1},j)\implies a_i\neq a_j\)

一个自然的想法是,我们依次询问 \([1,k],[2,k+1]\dots [n,k-1]\)(破环为链),就可以得到 \((a_1,a_k),(a_2,a_{k+1})\dots (a_n,a_{k-1})\)\(n-1\) 对关系。

此时我们根据这 \(n-1\) 对关系建立出一棵树,每条边形如:\((i,j,[a_i\neq a_j])\)

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

但是,很遗憾,这是错的,并没有保证 \(n\bmod k\neq 0\)。因为 \(n\bmod k=0\) 时会出现环。

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

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

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

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

    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

给定字符串 \(S\)\(\forall i\in [1,|S|],0\le s_i\le 9\),定义 \(f(S)\) 为:

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

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

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

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

故若有解,必然 \(\forall i,s_i\neq1\implies s_{i+1}=1\)

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

\(f_i\) 为删去 \([i+1,n]\) 的最小次数。 \(f_{n+1}=0\),目标 \(f_1\)

考虑当 \(s_i=1\) 时,进行一次操作足以删掉 \(s_i\),故 \(f_i=f_{i+1}+1\)

\(s_i>1\) 时,由于原性质,必然有 \(s_{i-1}=1,s_{i+1}=1\),则在删去 \([i+1,n]\) 之前,会复制 \(f_{i+1}\times (s_i-1)\) 个 1出来(排除原来的)。

在删掉 \([i+1,n]\) 之后,我们先执行一次操作删除 \(s_i\),还要执行 \((f_{i+1}+1)(s_i-1)\) 次操作删除末尾多生成的1。

所以 \(f_i=(f_{i+1}+1)(s_i-1)+f_{i+1}+1=s_i(f_{i+1}+1)\)

发现两种情况下,递推式等价:\(f_i=s_i(f_{i+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(1\le n\le 40)\) 张牌,每一张牌正面写上了数字 \(a_i\),背面写上了数字 \(b_i\)。最初所有牌都是正面朝上。

\(m\) 个机器,每个机器有参数 \(x_i,y_i(1\le x_i,y_i\le n)\)\(x_i\) 可以等于 \(y_i\)

每个机器只能启动一次,并且有 \(\frac{1}{2}\) 的概率将牌 \(x_i\) 翻转,同时有 \(\frac{1}{2}\) 的概率将牌 \(y_i\) 翻转。

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

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

\(cnt_i=\sum_{k\in Q}[x_k=i]|[y_k=i]\),则:

  1. \(cnt_i=0\implies p_i\)
  2. \(cnt_i>0\implies p_i=\frac{1}{2}\)

证明:

\[p_i=\sum_{k=0}^{\frac{cnt_i}{2}}\frac{{c\choose 2k+1}}{2^{cnt_i}}=\frac{2^{cnt_i-1}}{2^{cnt_i}}=\frac{1}{2} \]

综上,\(p_i=\frac{1}{2}[cnt_i>0]\)

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

推论:每一次有 \(\frac{1}{2}\) 的概率将 \(x(x\in{0,1})\) 异或 \(1\) ,则最终被异或1的概率为 \(\frac{1}{2}\)

我们现在来考虑计算这个最大期望。显然,若翻了牌 \(i\),则它的期望值为 \(\frac{a_i+b_i}{2}\),否则为 \(a_i\)

设集合 \(P\)\(cnt_i>0\)\(i\) 的集合,则期望:

\[\begin{align*} E(Q)&=\sum_{x\notin P}a_x+\sum_{x\in P}\frac{a_x+b_x}{2}\\&=\frac{\sum_{x\notin P}2a_x+\sum_{x\in P}(a_x+b_x)}{2} \\&=\frac{\sum_{x=1}^n2a_x-\sum_{x\in P}(a_x-b_x)}{2}\\&=\sum_{i=1}^na_i-\frac{\sum_{x\in P}a_x-b_x}{2} \end{align*} \]

\(s=\sum_{i=1}^na_i,w(P)=\sum_{x\in P}\frac{a_x-b_x}{2}\),我们便要求 \(s-w_{\min}\)

Trick:抽离常数项,直接算\(\Delta\) 一般可以简化计算

然后,我们考虑计算答案,记 \(d_i=\frac{|a_i-b_i|}{2}\)

先将牌分类\(S=\lbrace d_i\rbrace(a_i\ge b_i),T=\lbrace d_i\rbrace(a_i<b_i)\)

对于一台机器 \(i\),若 \(x_i\in T,y_i\in T\),显然就必选这台机器,此时我们将答案加上 \(d_{x_i}+d_{y_i}\),然后将二者对应的 \(d\) 值改为0避免重复统计。

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

\[w(P)=\sum_{x\in S\cap P}d_x-\sum_{x\in T\cap P}d_x \]

贪心地,我们要使得 \(\sum_{x\in T\cap P}d_x\) 尽量大。

这里用一个 启发式的思想

  1. \(|S|<|T|\)

显然,贪心地,若选择了 \(x\in S\cap P\),则与其相关的属于集合 \(T\) 的牌全部加入到 \(P\) 之中。

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

根据贪心,\(P\cap T=\bigcup_{x\in I}F(x)\)\(F(x)\) 可以 \(O(m)\) 预处理,枚举集合 \(I\) 的复杂度为 \(O(2^{|S|})\),计算贡献的复杂度为 \(n\),所以复杂度为 \(O(m+n2^{|S|})\)\(|S|\le \frac{n}{2}\)

  1. \(|S|>|T|\)

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

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

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

容易得到:

\[dp_{i+1,A}\leftarrow\min(dp_{i+1,A},dp_{i,A}) \]

\[dp_{i+1,A\cup F(S_i)}\leftarrow \min(dp_{i+1,A\cup F(S_i)},dp_{i,A}-d_{S_i}) \]

DP复杂度为 \(O(|S|2^{|T|})\)

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

\[w_{\min}=\min_{A\in T}\left\lbrace\sum_{i\in A}d_i+dp_{|S|+1,A}\right\rbrace \]

复杂度 \(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\) 堆,最终局面下 \(1\sim k\) 堆石子相同且必定小于 \(a_{k+1}\))

\(h(k)\) 为清空 \(k\) 堆石子的最终局面数,则我们设进行了 \(i\) 次操作1,显然 \(i\in [a_k,a_{k+1}-1]\)

设前缀和数组 \(s\),则此时袋子里有 \((n-k)i+s_{k}\) 个石子,总共可以进行 \(\lfloor\frac{(n-k)i+s_{k}}{n}\rfloor+1\) 次操作。

则:

\[h(k)=\sum_{i=a_k}^{a_{k+1}-1}\left\lfloor\frac{(n-k)i+s_{k}+n}{n}\right\rfloor=\sum_{i=a_k+1}^{a_{k+1}}\left\lfloor\frac{(n-k)i+s_{k}+k}{n}\right\rfloor \]

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

特别地,\(h(n)=\lfloor\frac{s_n}{n}\rfloor+1\)

答案为 \(\sum_{i=1}^nh(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 @ 2023-08-07 11:38  spdarkle  阅读(20)  评论(0编辑  收藏  举报