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]\),则:
- \(cnt_i=0\implies p_i\)
- \(cnt_i>0\implies p_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\) 的集合,则期望:
设 \(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避免重复统计。
现在,我们来考虑计算贡献。
贪心地,我们要使得 \(\sum_{x\in T\cap P}d_x\) 尽量大。
这里用一个 启发式的思想
- \(|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}\)
- \(|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(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";
}