第一次尝试做一整场AGC,结果做了一整天。。。还是开个博客记录一下吧。
A - Poisonous Cookies
一道难度评分高达90分的思博题。
#include<bits/stdc++.h>
using namespace std;
int a,b,c;
int main(){
scanf("%d%d%d",&a,&b,&c);
printf("%d",b+min(c,a+b+1));
return 0;
}
B - Tree Burning
一开始以为可以直接顺时针走一个,逆时针走一个,然后顺时针再走一个,逆时针再走一个,以此类推。然后观察样例可以发现,还可能会先沿某一个方向走若干个点,再开始上面的循环过程,这个贪心做法可以用数学归纳法证明是正确的。
于是接下来就枚举走到哪个点时开始循环,循环后的部分可以通过预处理前缀和与后缀和做到 \(\mathcal O(n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int L,n,x[N];
ll suf[N],pre[N];
int main(){
scanf("%d%d",&L,&n);
for(int i=1;i<=n;++i) scanf("%d",&x[i]);
ll ans=0;
for(int i=1;i<=n;++i) pre[i]=pre[i-1]+x[i];
for(int i=n;i>=1;--i) suf[i]=suf[i+1]+L-x[i];
for(int i=1;i<=n;++i){
ll now=0;
if((n-i+1)&1){
int t=n-(n-i)/2+1;
now+=2ll*suf[t]+2ll*(pre[t-1]-pre[i-1])-x[t-1];
}
else{
int t=n-(n-i+1)/2+1;
now+=2ll*suf[t]+2ll*(pre[t-1]-pre[i-1])-(L-x[t]);
}
if(i==n) now=x[n];
ans=max(ans,now);
}
for(int i=n;i>=1;--i){
ll now=0;
if(i&1){
int t=i/2;
now+=2ll*pre[t]+2ll*(suf[t+1]-suf[i+1])-(L-x[t+1]);
}
else{
int t=i/2;;
now+=2ll*pre[t]+2ll*(suf[t+1]-suf[i+1])-x[t];
}
if(i==1) now=L-x[1];
ans=max(ans,now);
}
printf("%lld\n",ans);
return 0;
}
C - Coloring Torus
神仙构造题。
如果 \(k\le 500\) 那么一个容易想到的做法是对每一行/每一列填同一个颜色,这样一定合法。考虑将这一个构造改装到对角线上(对角线是指 \(i+j\bmod n\) 相同的点)。但这样做依然只能染 \(n\) 个颜色。
如何变成 \(2n\) 个颜色呢?考虑对于每个对角线,给它分配两种颜色交替出现,比如下图中的 \(2,5\) 两种颜色,就可以通过此题。
1 2 3 4
2 3 5 1
3 4 1 2
5 1 2 3
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int k,n,a[N][N];
int main(){
scanf("%d",&k);
if(k<500){
n=k;
printf("%d\n",n);
for(int i=1;i<=k;puts(""),++i) for(int j=1;j<=k;++j) printf("%d ",i);
return 0;
}
else{
n=500;k-=n;
for(int i=1;i<=n;++i){
int ta=i,tb=i;
if(k) tb=n+i,k--;
for(int x=1,y=i;x<=n;++x,y=y%n+1)
a[x][y]=x&1?ta:tb;
}
printf("%d\n",n);
for(int i=1;i<=n;puts(""),++i) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);
}
return 0;
}
D - Inversion Sum
由于 \(n\) 很小,考虑直接对于位置 \(a_i,a_j\) 计算有多少种方案使得它们最终对答案做出贡献。
考虑倒着模拟整个过程,记 \(dp_{i,j}\) 表示所选择的两个数当前分别在位置 \(i\) 和 \(j\) 上时,有多少种方案使得最终位置满足前一个数大于后一个数。初始 \(dp_{i,j}=[i>j]\)。然后倒着扫一遍所有交换操作 \((x,y)\),可以发现我们只用对包含 \(x,y\) 的 \(\mathcal O(n)\) 个状态进行转移,这样一来复杂度就是 \(\mathcal O(nq)\) 的了。
最后再枚举 \(a_i,a_j\) ,根据它们的大小关系计算对答案的贡献即可。
#include<bits/stdc++.h>
using namespace std;
const int N=3010,mod=1e9+7,iv=(mod+1)/2;
int n,q,a[N],x[N],y[N],dp[N][N];
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=q;++i) scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<i;++j)
dp[i][j]=1;
for(int i=q;i>=1;--i){
int xx=x[i],yy=y[i];
for(int j=1;j<=n;++j){
if(j!=xx&&j!=yy){
int a=dp[xx][j],b=dp[yy][j];inc(a,b);
dp[xx][j]=dp[yy][j]=1ll*a*iv%mod;
a=dp[j][xx],b=dp[j][yy];inc(a,b);
dp[j][xx]=dp[j][yy]=1ll*a*iv%mod;
}
}
int a=dp[xx][yy],b=dp[yy][xx];inc(a,b);
dp[xx][yy]=dp[yy][xx]=1ll*a*iv%mod;
}
int ans=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
if(i==j) continue;
if(a[i]<a[j]) ans=add(ans,dp[i][j]);
else if(a[i]>a[j]) ans=add(ans,(mod+1-dp[i][j])%mod);
}
for(int i=1;i<q;++i) ans=add(ans,ans);
printf("%d\n",ans);
return 0;
}
E - Less than 3
神仙题,E比F难系列。考虑在序列上的 \(0,1\) 之间连一条红线,在 \(1,0\) 之间连一条蓝线,在序列的最左侧与最右侧分别增加若干条红蓝交替的线,那么一次操作就相当于将一条线左移或者右移,移动过程中要求不能有相邻的两条线的距离 \(>2\),下面是官方题解的图片:
于是我们只需要找一个 \(s\) 中的线与 \(t\) 中线的对应关系。找到关系后只需要固定位置不变的线,将其他线分成多个块,每个块内部向同一个方向移动即可完成构造。于是我们只需要 \(\mathcal O(n)\) 的枚举对应方案即可做到 \(\mathcal O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=5010;
char s[N],t[N];
int n,a[N],b[N],c1,c2;
int ans=0x3f3f3f3f;
int main(){
scanf("%d",&n);
scanf("%s%s",s+1,t+1);
for(int i=1;i<n;++i) if(s[i]!=s[i+1]) a[++c1]=i;
for(int i=1;i<n;++i) if(t[i]!=t[i+1]) b[++c2]=i;
for(int i=-c2;i<=c1+1;++i){
if((i&1)&&s[1]!=t[1]) continue;
if(i%2==0&&s[1]==t[1]) continue;
int now=0;
for(int j=min(1,i),k=min(1-(i-1),1);j<=c1||k<=c2;++j,++k){
int x=(j<1)?0:(j>c1?n:a[j]);
int y=(k<1)?0:(k>c2?n:b[k]);
now+=abs(x-y);
}
ans=min(ans,now);
}
printf("%d\n",ans);
return 0;
}
F - Permutation and Minimum
考虑将原问题看成将 \(1\sim 2n\) 的排列分成 \(n\) 对数。两个数已经确定的数对可以忽略,剩下还有确定了一个数以及一个都没有确定的数对。考虑 \(dp\),从小到大填入每一个数,设 \(dp_{i,j,k}\) 表示填了 \(\le i\) 的数,当前已确定最小值的有 \(j\) 对(设为 \(A\) 类数对), 已填一个数但不确定大小的有 \(k\) 个(设为 \(B\) 类数对)。
转移需要注意的是,如果将一个数放进 \(B\) 类数对,那我们无法确定是与哪一个数进行的匹配,可以将操作延后至遍历到与它配对的数时进行。遍历到某个被钦定了位置的数 \(x\) 时,如果还有 \(suf\) 个钦定数没有配对,那么就意味着有 \(suf-k\) 个前面的数与这些钦定的数进行匹配,因此可以讨论 \(x\) 是否与前面匹配,有转移:
\[dp_{i+1,j,k}\gets (suf-k)dp_{i,j,k}\\ dp_{i+1,j+1,k-1}\gets dp_{i,j,k} \]于是复杂度为 \(\mathcal O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=610,mod=1e9+7;
int dp[N][310][310];//考虑到第i个数字,当前已确定最小值的有j个, 已填一个数但未确定大小的有j个
int n,a[N],d[N],tot,c1,c2,pd[N],vis[N],inv[N];
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
int main(){
scanf("%d",&n);n<<=1;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(a[i]>0) pd[a[i]]=1;
}
inv[0]=1;
for(int i=1;i<=n;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i+=2){
if(a[i]>0&&a[i+1]>0){vis[a[i]]=vis[a[i+1]]=1;continue;}
else if(a[i]>0||a[i+1]>0) ++c2;
else ++c1;
}
for(int i=1;i<=n;++i) if(!vis[i]) d[++tot]=i;
dp[0][0][c2]=1;
int suf=c2;
for(int i=0;i<tot;++i){
for(int j=0;j<=c1+c2;++j)
for(int k=0,x;k<=c2;++k){
if(!(x=dp[i][j][k])) continue;
if(pd[d[i+1]]){
if(k!=suf) inc(dp[i+1][j][k],1ll*(suf-k)*x%mod);
if(k) inc(dp[i+1][j+1][k-1],x);
}
else{
if(j) inc(dp[i+1][j-1][k],x);
int tmp=(i-j)/2+j+k;
if(c1+c2-tmp)
inc(dp[i+1][j+1][k],x);
if(k) inc(dp[i+1][j][k-1],x);
}
}
if(pd[d[i+1]]) suf--;
}
int ans=dp[tot][0][0];
for(int i=1;i<=c1;++i) ans=1ll*ans*i%mod;
printf("%d\n",ans);
return 0;
}