AtCoder Beginner Contest 190
AtCoder Beginner Contest 190
不难。
A - Very Very Primitive Game
题意:两个人吃糖,每次吃一颗,\(C=0\)表示第一个人先吃,\(C=1\)表示第二个人先吃,先无糖可吃的人就输了,问谁赢。
简单分类讨论,如果差值大于\(1\)一定是糖多的赢,对\(abs(A-B)=1\)的特殊处理一下即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int main()
{
int A=read(),B=read(),C=read();
if(A-B>=2)return puts("Takahashi"),0;
if(B-A>=2)return puts("Aoki"),0;
if(C==1&&A>=B)return puts("Takahashi"),0;
if(C==1&&A<B)return puts("Aoki"),0;
if(C==0&&B>=A)return puts("Aoki"),0;
return puts("Takahashi"),0;
}
B - Magic 3
题意:一个人用\(n\)次技能打怪物,第\(i\)个技能触发时间为\(x_i\),威力为\(y_i\),怪物能免疫触发时间大于等于\(S\)技能的和威力小于等于\(D\)的技能,问是否有技能能够打到怪物身上。
按顺序读入,判断是否有满足条件的技能即可(ABC第二题比第一题简单是传统艺能吗)
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int n,S,D;
int main()
{
n=read();S=read();D=read();
while(n--)
{
int x=read(),y=read();
if(x<S&&y>D)return puts("Yes"),0;
}
return puts("No"),0;
}
C - Bowls and Dishes
题意:\(n\)个盘子\(m\)个条件,第\(i\)个条件被满足当且仅当第\(a_i\)个盘子和第\(b_i\)个盘子都不为空。有\(k\)个人过来,第\(i\)个人可以往第\(c_i\)个盘子里放一个球,或往第\(d_i\)个盘子里边放一个球,问最多有多少条件被满足。
感觉应该是有贪心或者其他的做法的,但是这题\(k\)很小,因此直接二进制枚举第\(i\)个人放到第\(c_i\)个中还是第\(d_i\)个中,之后\(O(n)\)统计一下答案即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,k,a[N],b[N],c[N],d[N],vis[101];
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
a[i]=read();
b[i]=read();
}
k=read();
for(int i=1;i<=k;i++)
{
c[i]=read();
d[i]=read();
}
int maxn=0;
for(int i=0;i<(1<<k);i++)
{
memset(vis,0,sizeof(vis));
for(int j=1;j<=k;j++)
{
if(i&(1<<(j-1)))vis[c[j]]=1;
else vis[d[j]]=1;
}
int ans=0;
for(int j=1;j<=m;j++)ans+=(vis[a[j]]&vis[b[j]]);
maxn=max(maxn,ans);
}
printf("%d\n",maxn);
return 0;
}
D - Staircase Sequences
题意:问和为\(N\)公差为\(1\)的等差数列的个数。
看到等差数列求和就直接列求和公式,设\(x\)为首项,\(k\)为项数,则:
把\(2\)移过去
那很明显\(k\)就是\(2N\)的一个因子,而一个满足条件的\(k\)需要让\(x\)为正整数,\(O(\sqrt {N})\)枚举\(2N\)的因子即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
#define int long long
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int n,ans;
int check(int k)
{
if(((n/k)+1-k)%2!=0)return 0;
return 1;
}
signed main()
{
n=read()*2ll;
for(int i=1;i*i<=n;i++)
{
if(n%i==0)
{
ans+=check(i);
if(i*i!=n)ans+=check(n/i);
}
}
printf("%lld\n",ans);
return 0;
}
E - Magical Ornament
题意:有\(N\)种珠子,\(M\)种排列方式,第\(i\)种珠子和第\(j\)种珠子可以挨在一起摆放当且仅当存在\(a_x=i,b_x=j\)或\(a_x=j,b_x=i\),现在有\(k\)种珠子要穿在一起,求串的最小长度。
正解好像是\(O(2^KK^2+K(N+M))\)的,我写的是\(O(2^KK^3+K(N+M))\)的\(dp+bfs\)。
我这个做法还是比较好想的,看到这熟悉的形式,就想到了将\(a_x\)和\(b_x\)间连一条无向边,那么取完\(i\)再取\(j\)的最优方式就是在这张图上把\(i\)和\(j\)最短路上的珠子都取了。
那首先就可以预处理出来\(dis(i,j)\)表示取完\(i\)再取\(j\)的最少取得数量,由于我们只需要关键点(就是那些要求的珠子)的\(dis\),因此对于每个关键点\(bfs\)一发即可求出\(dis\)。
由于\(k\)非常的小,可以用状压\(dp\),设\(f(i,S)\)表示选了\(i\)个珠子集合状态为\(S\)的最小价值,考虑转移时枚举最后一个珠子和倒数第二个珠子。
但是这样做是有问题的,比如说我们现在有\(4\)个珠子,枚举最后一个珠子是\(3\),而倒数第二个珠子是\(2\),那么转移就是从\(f(3,(1011)_2)+dis(2,3)\)转移过来的,但是问题在于:\(f(3,(1011)_2)\)有可能最后一个取得不是\(2\),那么我们就歇逼了。
那考虑直接再加一维表示最后一个取的珠子,即\(f(i,S,j)\)表示取了\(i\)个珠子,关键点集合状态为\(S\),最后一个取的珠子是\(j\),转移直接枚举最后两个取的即可。
答案直接枚举最后一个取的珠子即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define N 100005
#define MAXN 18
#define pb push_back
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,k,f[18][1<<18][18],C[N],dis[18][N],vis[N];
vector<int>G[N];
void bfs(int S)
{
queue<int>q;
memset(vis,0,sizeof(vis));
vis[C[S]]=1;
dis[S][C[S]]=0;
q.push(C[S]);
while(!q.empty())
{
int x=q.front();q.pop();int siz=G[x].size();
for(int i=0;i<siz;i++)
{
if(!vis[G[x][i]])
{
dis[S][G[x][i]]=dis[S][x]+1;
vis[G[x][i]]=1;
q.push(G[x][i]);
}
}
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
G[x].pb(y);G[y].pb(x);
}
k=read();
for(int i=1;i<=k;i++)C[i]=read();
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=k;i++)bfs(i);
memset(f,0x3f,sizeof(f));
for(int i=1;i<=k;i++)f[1][1<<(i-1)][i]=1;
for(int i=2;i<=k;i++)
{
for(int S=0;S<(1<<k);S++)
{
for(int j=1;j<=k;j++)
{
if(!(S&(1<<(j-1))))continue;
for(int l=1;l<=k;l++)
{
if(!(S&(1<<(l-1))))continue;
f[i][S][j]=min(f[i][S][j],f[i-1][S-(1<<(j-1))][l]+dis[j][C[l]]);
}
}
}
}
int ans=0x3f3f3f3f;
for(int i=1;i<=k;i++)
{
ans=min(ans,f[k][(1<<k)-1][i]);
}
printf("%d\n",ans==0x3f3f3f3f?-1:ans);
return 0;
}
F - Shift and Inversions
题意:一个排列\(a\),定义一个\(a\)的置换\(b\)为\(b_i = a_{i+k \bmod N}\),求对于每一个\(k\in[0,N-1]\),所对应的\(b\)的逆序对数量。
说实话没E题难。
首先这个\(b\)就是每一次把\(a\)数组的第一个数取出来放到最后一个。
为了方便,我们先把\(a\)中所有数都加\(1\),让它变成一个排列,方便操作。
那么考虑当前第一个数为\(x\),把他扔到最后一个会有怎样的影响呢?
首先因为第一个数为\(x\),那么\(1 \to x-1\)中所有数一定都在第一个的后边(废话),因此扔到后边逆序对数会减少\(x-1\),但同时因为比\(x\)大的数也都在后边,那会多出来\(n-x\)对逆序对。
因此只需要处理\(k=0\)的情况,之后依次看第一个数即可。
静态序列的逆序对数量是个经典题,树状数组解决即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#define lowbit(x) x&-x
#define N 300005
#define int long long
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
int n,a[N],ans;
struct BIT
{
int c[N];
void modify(int x,int k){for(;x<=n;x+=lowbit(x))c[x]+=k;}
int query(int x){int res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
}T;
signed main()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read()+1;
for(int i=1;i<=n;i++)
{
ans+=i-1-T.query(a[i]);
T.modify(a[i],1);
}
for(int i=1;i<=n;i++)
{
printf("%lld\n",ans);
ans-=(a[i]-1);ans+=n-a[i];
}
}
感觉不是非常难,下次vp一下ARC。