AtCoder Grand Contest 017
Preface
这场看Standings感觉好简单啊(确实很简单),但是EF还是懒了没仔细想
A - Biscuits
显然可以直接DP出到当前位置和为奇数/偶数的方案数
#include<cstdio>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
int n,x,p; long long f[2][2];
int main()
{
RI i,j; for (scanf("%d%d",&n,&p),f[0][0]=i=1;i<=n;++i)
for (scanf("%d",&x),x&=1,j=0;j<2;++j) f[i&1][j]=f[i&1^1][j]+f[i&1^1][j^x];
return printf("%lld",f[n&1][p]),0;
}
B - Moderate Differences
差点又被B卡住,多亏陈指导出手相救
刚开始naive地以为能转移到的时一个区间,后来被样例叉掉了
后来发现可以变化到的区间一定形如\([x\times c+y\times (-d),x\times d+y\times (-c)]\),其中\(x+y=n-1\),因此直接枚举其中一个判断即可
#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int n,a,b,c,d;
int main()
{
scanf("%d%d%d%d%d",&n,&a,&b,&c,&d); for (RI i=0;i<=n-1;++i)
if (1LL*c*i-1LL*d*(n-1-i)<=b-a&&b-a<=1LL*d*i-1LL*c*(n-1-i)) return puts("YES"),0;
return puts("NO"),0;
}
C - Snuke and Spells
刚开始又naive了,还好后来抢救回来了
首先我们考虑对于某个权值\(x\),设它的个数为\(c_x\),显然我们可以把它看作\(x-c_x+1,x-c_x+2,\cdots,x-1,x\)
那么我们现在就把问题转化为:有若干个线段\([x-c_x+1,x]\),而\([1,n]\)上所有未被覆盖的位置都要花费\(1\)的代价去修改
单点修改全局询问直接模拟即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,m,a[N],c[N],x,y,ans,p[N<<1];
inline void remove(CI x)
{
if (!--p[n+x-c[x]+1]) ans+=x-c[x]+1>=1; --c[x];
}
inline void expand(CI x)
{
++c[x]; if (++p[n+x-c[x]+1]==1) ans-=x-c[x]+1>=1;
}
int main()
{
RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&a[i]),++c[a[i]];
for (ans=n,i=1;i<=n;++i) for (j=i-c[i]+1;j<=i;++j) if (++p[n+j]==1) ans-=j>=1;
for (i=1;i<=m;++i) scanf("%d%d",&x,&y),remove(a[x]),expand(a[x]=y),printf("%d\n",ans);
return 0;
}
D - Game on Tree
莫名奇妙就搞出来的一道博弈题,后来发现是个经典模型
考虑构造\(SG\)函数,我们显然要考虑在合并子树时计算当前节点的答案
假设当前点有\(k\)个子节点,显然我们可以把根节点复制\(k\)份,每个根节点下面接一个子节点,这样每棵子树就独立了
在只有一个子节点时,父节点与子节点的\(SG\)函数关系有:
- 若直接断开连接父节点和子节点的边,此时后继状态\(SG=0\)
- 若断开其它边,我们发现子节点内的所有\(SG\)值都有可能取到
再加上父节点和子节点的边,我们发现父节点的\(SG\)值就是子节点\(SG\)值加\(1\)
对于多个子树的显然直接异或起来即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct edge
{
int to,nxt;
}e[N<<1]; int n,x,y,head[N],cnt;
inline void addedge(CI x,CI y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
#define to e[i].to
inline int SG(CI now,CI fa=0)
{
int ret=0; for (RI i=head[now];i;i=e[i].nxt)
if (to!=fa) ret^=SG(to,now)+1; return ret;
}
#undef to
int main()
{
RI i; for (scanf("%d",&n),i=1;i<n;++i)
scanf("%d%d",&x,&y),addedge(x,y);
return puts(SG(1)?"Alice":"Bob"),0;
}
E - Jigsaw
MD刚开始都想到化点为边了最后还是脑抽了去想走过所有点的路径了实在是太SB了
首先我们先考虑拼图之间的匹配关系,但暴力建边显然会挂,因此我们把一块拼图看作一条边
具体地,若某块拼图\(c_i=0\),则令\(x=-a_i\),否则令\(x=c_i\);若\(d_i=0\),则令\(y=b_i\),否则令\(y=-d_i\)
然后我们建出一条\(x\to y\)的有向边,此时我们需要选择若干条链(从负数权值点出发,到正数权值点结束),使得所有的边都被选择过
首先发现对于所有权值为负数的点要满足出度大于等于入度,对于所有权值为正数的点要满足入度大于等于出度
同时某个联通块中不能出现环,有向图出现环的充要条件就是联通块内所有点入度等于出度,直接用并查集判一下即可
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=405;
int n,h,a,b,c,d,in[N],out[N],fa[N]; bool vis[N];
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
RI i; for (scanf("%d%d",&n,&h),i=-h;i<=h;++i) fa[h+i]=h+i;
for (i=1;i<=n;++i)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
int l=(c?c:-a)+h,r=(d?-d:b)+h; ++out[l]; ++in[r];
if (getfa(l)!=getfa(r)) fa[getfa(l)]=getfa(r);
}
for (i=-h;i<0;++i) if (in[h+i]>out[h+i]) return puts("NO"),0;
for (i=1;i<=h;++i) if (in[h+i]<out[h+i]) return puts("NO"),0;
for (i=-h;i<=h;++i) if (in[h+i]!=out[h+i]) vis[getfa(h+i)]=1;
for (i=-h;i<=h;++i) if (in[h+i]&&out[h+i]&&!vis[getfa(h+i)])
return puts("NO"),0; return puts("YES"),0;
}
F - Zigzag
刚开始只想了\(O(2^{2n})\)的暴力做法,后来发现正解其实也不难的说
容易发现我们把向下看作\(0\),向右下看作\(1\),那么一个长度为\(n-1\)的操作序列显然可以看作一个二进制数
我们发现对于两个相邻的位置\(i-1,i\),需要满足\(i-1\)的操作序列的前缀和均小于等于\(i\)的操作序列
我们考虑若\(i-1\)和\(i\)的前\(j-1\)个位置都相等,对于第\(j\)位若它们相等那么状态依然是合法的,若\(i\)的第\(j\)位为\(0\)而\(i-1\)的第\(j\)位为\(1\)时此时该状态显然不合法
考虑当\(i\)的第\(j\)位为\(0\)而\(i-1\)的第\(j\)位为\(1\)时,\(i\)的哪些状态都是合法的。我们可以找到\(i-1\)的状态的第\(j\)位之后的第一个\(1\)的位置\(p\),然后把\(i-1\)的第\(p\)位上的\(1\)强行推到第\(j\)位上
在下一个\(i-1\)存在\(1\)的位置之间,显然时没有任何影响的,因为这一位多了一个\(1\),而后面都是\(0\),因此怎么填都是合法的
若下一个\(1\)不存在,显然就可以随便走了,因此我们可以直接这样DP
设\(f_{i,j,k}\)表示当前考虑到第\(i\)条折线,其状态的前\(j\)位和第\(i-1\)条折线的状态相同,\(i-1\)的状态为\(k\)的方案数
转移的时候枚举这一位填什么即可,总复杂度\(O(2^n\times nm)\)
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=20,mod=1e9+7;
int n,m,k,a,b,c,lim,ans,p[N+1][N],f[2][N][1<<N],nxt[N][1<<N];
int main()
{
RI i,j,k,s; for (scanf("%d%d%d",&n,&m,&k),memset(p,-1,sizeof(p)),--n,i=1;i<=k;++i)
scanf("%d%d%d",&a,&b,&c),p[a][b-1]=c; for (lim=1<<n,i=0;i<lim;++i)
for (j=0;j<n;++j) for (nxt[j][i]=-1,k=j+1;k<n&&!~nxt[j][i];++k)
if ((i>>k)&1) nxt[j][i]=k; for (f[0][n][0]=i=1;i<=m;++i)
{
for (j=0;j<lim;++j) f[i&1][0][j]=f[i&1^1][n][j]; memset(f[i&1^1],0,sizeof(f[i&1^1]));
for (j=0;j<n;++j) for (k=0;k<lim;++k) if (f[i&1][j][k])
for (s=0;s<2;++s) if (!~p[i][j]||s==p[i][j])
{
int ls=(k>>j)&1; if (ls&&!s) continue;
if (ls==s) { (f[i&1][j+1][k]+=f[i&1][j][k])%=mod; continue; }
if (~nxt[j][k]) (f[i&1][j+1][k+(1<<j)-(1<<nxt[j][k])]+=f[i&1][j][k])%=mod;
else (f[i&1][j+1][k+(1<<j)]+=f[i&1][j][k])%=mod;
}
}
for (i=0;i<lim;++i) (ans+=f[m&1][n][i])%=mod; return printf("%d",ans),0;
}
Postscript
还剩一场加油啊