AtCoder Beginner Contest 261(ABC261)A-Ex 题解
A - Intersection
处理成 \([L,R)\) 区间,枚举即可。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int len;
int main()
{
int l1=read(),r1=read(),l2=read(),r2=read();
for(int i=0;i<=100;i++)
if(l1<=i&&i<r1&&l2<=i&&i<r2)len++;
printf("%d",len);
return 0;
}
B - Tournament Result
赢记成 \(1\),输记成 \(-1\),平局为 \(0\)。相当于求满足 \(A_{i,j}+A_{j,i}=0\) 的对数。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n,a[1005][1005];
char s[1005];
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=n;j++)
{
if(s[j]=='W')a[i][j]=1;
if(s[j]=='D')a[i][j]=0;
if(s[j]=='L')a[i][j]=-1;
}
}
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
if(a[i][j]+a[j][i]!=0)
{
printf("incorrect");
return 0;
}
printf("correct");
return 0;
}
C - NewFolder(1)
用 map 模拟即可。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n;
map<string,int> mp;
string s;
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
cin>>s;
if(!mp.count(s))cout<<s<<"\n";
else cout<<s<<"("<<mp[s]<<")"<<"\n";
mp[s]++;
}
return 0;
}
D - Flipping and Bonus
设置状态 \(dp_i\) 表示经过 \(i\) 次抛硬币的最大收益。
当第 \(i\) 次抛出为 \(0\) 时,\(dp_i=dp_{i-1}\)。
否则,转移时第二维枚举当前连续抛出 \(1\) 的次数为 \(j\),即第 \(i-j\) 次为 \(0\),第 \(i-j+1\) 至第 \(i\) 次均为 \(1\)。
那么转移为 \(dp_i=\max\limits_{j=1}^{i}{dp_{i-j-1}+\sum\limits_{k=i-j+1}^i{a_i}+\sum\limits_{k=1}^j{c_i}}\),那个 \(j=i\) 的情况特判一下就行。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
typedef long long ll;
const int M=5005;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n,m;
ll a[M],c[M],dp[M],tot,ans;
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=a[i-1]+read();
for(int i=1,x;i<=m;i++)
x=read(),c[x]=read();
for(int i=1;i<=n;i++)
c[i]+=c[i-1];
for(int i=1;i<=n;i++)
{
dp[i]=max(dp[i-1],a[i]+c[i]);
for(int j=1;j<i;j++)
Max(dp[i],dp[i-j-1]+c[j]+a[i]-a[i-j]);
}
printf("%lld",dp[n]);
return 0;
}
E - Many Operations
按位考虑。预处理出第 \(i\) 位为 \(0/1\) 时在经过第 \(1\sim j\) 个操作后的值 \(to_{0/1,j}\)。然后按题意,对 \(X\) 第 \(i\) 位上的数 \(num\),用类似倍增的递推方式传递,即第 \(j\) 轮时有 \(num=to_{num,j}\),然后将贡献加到对应答案里。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,x,op[N],a[N],to[2][N],ans[N];
int main()
{
scanf("%d %d",&n,&x);
for(int i=1;i<=n;i++)
scanf("%d %d",&op[i],&a[i]);
for(int i=0;i<31;i++)
{
for(int now=0;now<2;now++)
{
int num=now;
for(int j=1;j<=n;j++)
{
int tmp=a[j]&(1<<i)?1:0;
if(op[j]==1) num&=tmp;
if(op[j]==2) num|=tmp;
if(op[j]==3) num^=tmp;
to[now][j]=num;
}
}
int tmp=x&(1<<i)?1:0;
for(int j=1;j<=n;j++)
{
tmp=to[tmp][j];
ans[j]+=(1<<i)*tmp;
}
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
F - Sorting Color Balls
答案显然为总逆序对数减去同色的逆序对数。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
typedef long long ll;
const int M=3e5+5;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n,c[M],x[M];
vector<int> E[M];
ll ans;
struct Tree
{
ll c[M];
#define lowbit(x) x&(-x)
void clear(){memset(c,0,sizeof(c));}
void add(int x,ll k){for(int i=x;i<=n;i+=lowbit(i))c[i]+=k;}
ll sum(int x){ll res=0;for(int i=x;i>0;i-=lowbit(i))res+=c[i];return res;}
}T;
int main()
{
n=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=n;i++)x[i]=read();
for(int i=1;i<=n;i++)
ans+=(i-1)-T.sum(x[i]),T.add(x[i],1);
T.clear();
for(int i=1;i<=n;i++)E[c[i]].push_back(x[i]);
for(int col=1;col<=n;col++)
{
if(E[col].empty())continue;
for(int i=0;i<(int)E[col].size();i++)
ans-=i-T.sum(E[col][i]),T.add(E[col][i],1);
for(int v:E[col])T.add(v,-1);
}
printf("%lld",ans);
return 0;
}
G - Replace
较为复杂的区间 DP。
首先想到倒着替换,从 \(t\) 经过一系列操作变成 \(s\)。设置状态 \(dp(l,r,c)\) 表示把 \(t\) 中片段 \([l,r]\) 替换为字符 \(c\) 所需的最少步数。那么我们先用替换后长度减小的状态更新,再用替换后长度不变的更新,来保证多种情况叠加的正常转移。
先处理长度减小的状态更新。枚举选用的操作 \(i\),设置状态 \(g(j,k)\) 表示把 \(t\) 中片段 \([l,k]\) 替换为 \(A_i\) 中片段 \([1,j]\) 所需的最少步数,那么有转移 \(g(j,k)=\min\limits_{k2=l-1}^{k-1}\Big\{g(j-1,k2)+dp\big(k2+1,k,A_{i,j}\big) \Big\}\)。如果存在 \(j\) 使得无法从有效状态转移而来,说明操作 \(i\) 当前无效,那么 \(g\) 就不能转移至 \(dp\) 中。
再处理长度不变的状态更新。用 Floyd 预处理从字符 \(j\) 替换成字符 \(i\) 所需的最少步数 \(dis(j,i)\),那么便有 \(dp(l,r,i)=\min\{dp(l,r,j)+dis(j,i)\}\)。
最终答案得到的方法也是类似的,具体见代码吧。代码里对 \(g\) 进行了滚动数组优化。
PS:由于这是我看别人代码而理出来的思路,难免有解释不当或疏漏之处,还请读者指教。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
const int inf=0x3f3f3f3f;
const int M=55;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n,m,p,leng[M],dis[30][30];
char s[M],t[M],cr[M],to[M][M];
int dp[M][M][30];// change [i,j] to c
int g[2][M];
int main()
{
memset(dis,0x3f,sizeof(dis));
memset(dp,0x3f,sizeof(dp));
scanf("%s",s+1);n=strlen(s+1);
scanf("%s",t+1);m=strlen(t+1);
p=read();
for(int i=1;i<=p;i++)
{
scanf("%s %s",cr+i,to[i]+1);
leng[i]=strlen(to[i]+1);
if(leng[i]==1)dis[to[i][1]-'a'][cr[i]-'a']=1;
}
for(int k=0;k<26;k++)
for(int i=0;i<26;i++)
for(int j=0;j<26;j++)
Min(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=m;i++)dp[i][i][t[i]-'a']=0;
for(int len=1;len<=m;len++)
for(int l=1,r=l+len-1;r<=m;l++,r++)
{
for(int i=1;i<=p;i++)
if(leng[i]<=len)
{
for(int i=l;i<=r;i++)g[0][i]=g[1][i]=inf;
g[0][l-1]=0;int now=0,fff=1;
for(int j=1;j<=leng[i];j++)
{
now^=1;bool ok=false;
for(int k1=l-1;k1<=r;k1++)
{
g[now][k1]=inf;
for(int k2=l-1;k2<k1;k2++)
Min(g[now][k1],g[now^1][k2]+dp[k2+1][k1][to[i][j]-'a']);
if(g[now][k1]<inf)ok=true;
}
if(!ok){fff=0;break;}
}
if(fff)Min(dp[l][r][cr[i]-'a'],g[now][r]+1);
}
for(int i=0;i<26;i++)
for(int j=0;j<26;j++)
Min(dp[l][r][i],dp[l][r][j]+dis[j][i]);
}
memset(g,0x3f,sizeof(g));
g[0][0]=0;int now=0;
for(int i=1;i<=n;i++)
{
now^=1;
for(int j=0;j<=m;j++)
{
g[now][j]=inf;
for(int k=0;k<j;k++)
Min(g[now][j],g[now^1][k]+dp[k+1][j][s[i]-'a']);
}
}
if(g[now][m]>=inf)printf("-1");
else printf("%d",g[now][m]);
return 0;
}
Ex - Game on Graph
直接按照题意搜索。由于有环,需要开个 \(dp_{x,op}\) 表示到点 \(x\) 轮到 \(op\) 操作时的对应答案来记忆化一下。初始化时全赋极大值,那么搜索时当高桥经过一个环再次走到同一点就是对自己不利的操作,而对青木有利。注意 \(dp\) 值不能边做边更新,否则遇到环时就破坏了上述做法的用意。
#include<bits/stdc++.h>
#define Max(a,b) ((a<b)&&(a=b))
#define Min(a,b) ((a>b)&&(a=b))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int M=2e5+5;
inline int read()
{
int x=0,f=1;static char ch;
while(ch=getchar(),ch<48)if(ch==45)f=0;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getchar(),ch>=48);
return f?x:-x;
}
int n,m,S;
struct Node{int to,w;};
vector<Node> E[M];
ll dp[M][2];
bool vis[M][2];
ll dfs(int x,int op)
{
if(vis[x][op])return dp[x][op];
vis[x][op]=true;
if(E[x].empty())return dp[x][op]=0;
ll res=op?0:inf,tmp;
for(auto [to,w]:E[x])
{
tmp=dfs(to,op^1)+w;
if(op)Max(res,tmp);
else Min(res,tmp);
}
return dp[x][op]=res;
}
int main()
{
n=read(),m=read(),S=read();
for(int i=1,a,b,c;i<=m;i++)
{
a=read(),b=read(),c=read();
E[a].push_back((Node){b,c});
}
memset(dp,0x3f,sizeof(dp));
ll ans=dfs(S,0);
if(ans>=inf)printf("INFINITY");
else printf("%lld",ans);
return 0;
}