AtCoder Grand Contest 008
AtCoder Grand Contest 008
A - Simple Calculator
翻译
有一个计算器,上面有一个显示按钮和两个其他的按钮。初始时,计算器上显示的数字是\(x\),现在想把这个数字给变成\(y\)。两个按钮的作用分别是让这个数加一和把这个数取反。问最少的按按钮的次数。
题解
神仙特判题,想清楚再写。
#include<iostream>
using namespace std;
int x,y,ans=2147483647;
int main()
{
cin>>x>>y;
if(x==y){puts("0");return 0;}
if(y>x)ans=min(y-x,abs(y+x)+1);
else ans=min(abs(x+y)+1,2+abs(x-y));
printf("%d\n",ans);
return 0;
}
B - Contiguous Repainting
翻译
有\(n\)个格子,每个格子上面都有一个数字,初始时所有格子都是黑色。每次可以选择连续的格子,把他们都染成黑色或者白色。最大化最终状态下黑格子上面的数字和。
题解
我们抛去一个长度为\(K\)的区间,那么显然的,除了这个区间之外的任何一个格子我们都能够控制它的颜色,而这个区间只能整体为白或者整体为黑,那么讨论一下即可。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 101000
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
ll ans,sum,s[MAX],ss[MAX];
int n,k,a[MAX];
int main()
{
n=read();k=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i)s[i]=s[i-1]+(a[i]>0?a[i]:0);
for(int i=1;i<=n;++i)ss[i]=ss[i-1]+a[i];
for(int i=1;i<=n-k+1;++i)
{
sum=s[i-1]+(s[n]-s[i+k-1]);
if(ss[i+k-1]-ss[i-1]>0)sum+=ss[i+k-1]-ss[i-1];
ans=max(ans,sum);
}
cout<<ans<<endl;
return 0;
}
C - Tetromino Tiling
翻译
有\(7\)种俄罗斯方块,你现在要用他们拼出一个\(2*k\)的矩形,最大化\(k\)。
每种方块可以旋转但是不能对称的翻转。(输出的是\(k/2\))
题解
\(C\)比\(B\)简单系列。
首先\(2*2\)的是无脑放。剩下的可以放进去的显然只有\(1*4\)和两个\(L\)型的。发现这三个的匹配方法有两个自己匹配和三个各一个拼在一起匹配,算一下即可。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll ans;
int a[8];
int main()
{
for(int i=1;i<=7;++i)scanf("%d",&a[i]);
ans+=a[2];ans+=2*(a[1]>>1);ans+=2*(a[4]>>1);ans+=2*(a[5]>>1);
if((a[1]&1)&&(a[4]&1)&&(a[5]&1))ans+=3;
else if((a[1]&1)&&(a[4]&1)&&(!(a[5]&1))&&a[5])ans+=1;
else if((a[1]&1)&&(!(a[4]&1))&&(a[5]&1)&&a[4])ans+=1;
else if((!(a[1]&1))&&(a[4]&1)&&(a[5]&1)&&a[1])ans+=1;
cout<<ans<<endl;
return 0;
}
D - K-th K
翻译
给定一个长度为\(n\)的序列\(x\),构造一个长度为\(n*n\)的序列\(a\),保证\([1,n]\)中的每个数都恰好出现了\(n\)次,并且第\(i\)个\(i\)出现的位置是\(x[i]\)。
题解
我说前面几题里面最傻逼的就是\(D\)题?
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 505
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,pos=1,a[MAX*MAX],p[MAX],x[MAX];
bool vis[MAX];
bool cmp(int a,int b){return x[a]<x[b];}
int main()
{
n=read();
for(int i=1;i<=n;++i)x[i]=read(),a[x[i]]=i,p[i]=i;
sort(&p[1],&p[n+1],cmp);
for(int i=1;i<=n;++i)
{
int v=p[i];
for(int j=1;j<v;++j)
{
while(a[pos])++pos;
a[pos]=v;
}
if(pos>x[v]){puts("No");return 0;}
}
for(int i=1;i<=n;++i)
{
int v=p[i];
for(int j=1;j<=n-v;++j)
{
while(a[pos])++pos;
if(pos<x[v]){puts("No");return 0;}
a[pos]=v;
}
}
puts("Yes");
for(int i=1;i<=n*n;++i)printf("%d ",a[i]);puts("");
return 0;
}
E - Next or Nextnext
翻译
给定一个长度为\(n\)的序列\(a\)。求满足要么\(P_i=a_i\),要么\(P_{P_i}=a_i\)的排列\(P\)的个数。
题解
我们把所有的\(i\)向\(a_i\)连边,那么对于一个合法的\(P\),两点之间要么跳一步,要么跳两步。
那么我们考虑这两种跳法可以构成几种不同的情况(对于一个环考虑)。
- 所有点都跳了一步。那么显然就是原图。
- 所有点都跳了两部。那么如果当前的环是一个奇环,那么显然它跳完之后还是一个奇环,只不过是点的顺序改变了一下,即得到了一个同构的环。如果当前的环是一个偶环,那么他就被拆分成了两个小环。
- 一部分是奇环,一部分是偶环。那么这样子会形成一个环,然后在它的外面形成了一些"脚",即连进来的一些边。同时所有的"脚"都不会从同一个节点伸展出来,并且所有的脚都是一条链的形式。
把\(i\)连向\(a_i\)构成的图称之为原图,\(P\)构建出来的图称为新图,显然原图是由若干个部分组成的。同理\(P\)构建出来的图也只能基于原图,通过上面三种情况(如果奇偶环分开考虑就是四种),归类之后只有两种情况,一种是环,另外一种是长出了"脚",即基环内向树,这些东西构成。
先考虑原图构成的环,对于每个环要么就是原图,要么对于奇环而言是同构的图,要么是通过偶环被拆分成了两个等大小的图。那么我们把所有等大小的环一起考虑,考虑所有的方案数。在合并两个等大小的环的时候注意一下有环大小种方法合并。
再考虑基环内向树的情况,显然两两之间不能合并,所以我们分开考虑每一个的答案。首先对于两条腿,我们最多只有两种方法将一条腿和环合并(这里画不了图,直接看\(atcoder\)上面的题解就有图)。现在考虑合并两条腿的情况,设\(l1\)为第二条腿的长度,\(l2\)是两条腿在挂在环上的那个点的之间的距离。如果\(l1\lt l2\),那么有\(2\)种方法合并进来,如果\(l1=l2\),那么只有一种方法,否则\(0\)种方法。
代码里面有注释。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MOD 1000000007
#define MAX 100100
#define ll long long
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,ans=1;
int a[MAX],dg[MAX];
bool cir[MAX];
int col[MAX],foot[MAX],cnt[MAX];
int f[MAX];
int main()
{
n=read();
for(int i=1;i<=n;++i)a[i]=read(),++dg[a[i]];
for(int i=1;i<=n;++i)
{
if(col[i])continue;
int x=i;
for(;!col[x];x=a[x])col[x]=i;
if(col[x]!=i)continue;//circle with foot
for(;!cir[x];x=a[x])cir[x]=true;//circle
}
for(int i=1;i<=n;++i)//illegal part
if((cir[i]&&dg[i]>2)||(!cir[i]&&dg[i]>1)){puts("0");return 0;}
for(int i=1;i<=n;++i)
{
if(dg[i])continue;//start of a foot do not have egde in
int x=i,len=0;
while(!cir[x])x=a[x],++len;
foot[x]=len;//record length
}
for(int i=1;i<=n;++i)//calculate answer of circle with feet
{
if(!cir[i])continue;//calculate circles
int x=i,pts=0,st=0,l1=0,fr=0;
//st:last root of foot,fr:first root of foot,l1:length of the first foot
//pts:number of the node in the circle
while(cir[x])
{
++pts;cir[x]=false;
if(foot[x])//a foot in the circle
{
if(!fr)st=fr=pts,l1=foot[x];
else
{
int ways=(foot[x]<(pts-st))+(foot[x]<=(pts-st));
ans=ans*ways%MOD;st=pts;
}
}
x=a[x];
}
if(fr)//first foot of the circle
{
int ways=(l1<(pts+fr-st))+(l1<=(pts+fr-st));
ans=ans*ways%MOD;
}
else//just a circle
cnt[pts]+=1;
}
for(int i=1;i<=n;++i)//dp,in order to calc each length of circles
{
if(!cnt[i])continue;
f[0]=1;
for(int j=1;j<=cnt[i];++j)
{
if(i>1&&(i&1))//odd circle
f[j]=(f[j-1]<<1)%MOD;//two ways
else//even circle
f[j]=f[j-1];//only one way
if(j>1)f[j]=(f[j]+1ll*f[j-2]*(j-1)%MOD*i%MOD)%MOD;
//merge two circle in the same length
}
ans=1ll*ans*f[cnt[i]]%MOD;
}
printf("%d\n",ans);
return 0;
}
F - Black Radius
题面
给定一个\(n\)个节点的树。有些点可以染色,每次染色操作为选定一个可以染色的点,以及一个距离,把距离这个点不超过钦定值的所有点全部染黑。初始时所有点都是白色。恰好可以进行一次染色,求最终所有点染色情况的方案数。
题解
抄题解.jpg。
定义\(f(x,d)\)表示距离\(x\)不超过\(d\)的点集。显然答案就是询问本质不同的\(f(x,d)\)的个数。考虑\(S=f(x,d1)=f(y,d_2)\),那么对于\(x\)到\(y\)路径上的任何一点\(K\),都有\(S=f(K,d_1-dist(x,K))\)。
我们现在在假设所有点都可以染黑的情况下来讨论答案。首先\(f(x,d)\)不考虑全集,最后再把全集加入答案即可。另外,对于\(x\)的任意一个相邻节点\(v\),不存在\(f(x,d)=f(v,d-1)\)。第一个限制很容易理解。考虑如何理解第二个限制,根据上面那个有关于两者等价的式子,需要满足路径上任意两点都等于当前点集,那么,当不存在相邻点\(v\)满足\(f(x,d)=f(v,d-1)\)的时候,证明\(x\)一定在这条路径的中点,因此也只会被考虑计算一次。
满足第一个限制的\(d\)的范围很好计算,只需要求出距离当前点的最远点即可。考虑第二个限制如何计算。想清楚什么时候会有\(f(x,d)=f(v,d-1)\)。如果以\(x\)为根,考虑距离\(v\)不超过\(d-1\)的所有点,如果除\(v\)自己的子树之外,所有\(x\)的子树都被染黑了,那么此时等式成立。那么在第二个限制中,\(d\)的范围是什么呢?显然不能超过到达它自己子树的最远距离。
这样只考虑了\(d\)的上界,而在一个点可以被染黑的时候,显然\(d\)的下界是\(0\)。现在考虑\(d\)不能被染黑时它的下界。我们先钦定\(x\)作为当前的树根。如果\(f(x,d)\)合法的话,必定满足一个可以被染黑的节点它的整棵子树都被染黑了,那么这里的下界就是这个可以被染黑的子树的最大深度。
然而我也不太懂(抄题解)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200100
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
const int inf=1e9;
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1,dg[MAX];
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;++dg[u];}
int n,c[MAX],fa[MAX],sz[MAX];ll ans;
int S[MAX],top;
char s[MAX];
int d1[MAX];//子树中距离最远的一个点
int d2[MAX];//同上,可以从父亲转移
int d3[MAX];//子树中有经过至少一个黑点的最远点
int d5[MAX];//上界
int d6[MAX];//下界
void dfs1(int u,int ff)
{
fa[u]=ff;sz[u]=c[u];
if((u!=1&&dg[u]==1)||(u==1&&!dg[u]))//叶子节点
{
d1[u]=0;d3[u]=c[u]?0:inf;
return;
}
int mx=0,mx2=inf;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs1(v,u);sz[u]+=sz[v];
mx=max(mx,d1[v]);
if(d3[v]<inf)mx2=min(mx2,d1[v]);
}
d1[u]=mx+1;d3[u]=mx2+1;
if(c[u])d3[u]=min(d3[u],d1[u]);
}
void dfs2(int u,int ff)
{
d2[u]=max(d2[u],(ff!=0)+d2[ff]);//从父亲转移过来
if(ff)d5[u]=max(d5[u],d2[u]-1);//维护上界
top=0;for(int i=h[u];i;i=e[i].next)if(e[i].v!=ff)S[++top]=e[i].v;
for(int i=1,mx=-1;i<=top;++i)//前缀其他儿子转移一下
{
int v=S[i];
d2[v]=max(d2[v],mx+2);
mx=max(mx,d1[v]);
}
for(int i=top,mx=-1;i>=1;--i)//后缀其他儿子转移一下
{
int v=S[i];
d2[v]=max(d2[v],mx+2);
mx=max(mx,d1[v]);
}
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=ff)dfs2(e[i].v,u);
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
scanf("%s",s+1);int tot=0;
for(int i=1;i<=n;++i)tot+=(c[i]=s[i]-48);
if(!tot){puts("0");return 0;}
memset(d3,63,sizeof(d3));
dfs1(1,0);dfs2(1,0);
for(int i=1;i<=n;++i)
{
if(c[i])d6[i]=0;//可以涂色的点
else d6[i]=min(d3[i],(sz[i]==sz[1])?inf:d2[i]);
//不能涂色的点考虑下界
int u=max(d1[i],d2[i])-1;//u为上界,初始为最短点距离减一
for(int j=h[i];j;j=e[j].next)
{
int v=e[j].v;
if(v==fa[i])u=min(u,d1[i]+1);//如果从父亲转移过来,
else u=min(u,d5[v]+1);
}
if(u>=d6[i])ans+=u-d6[i]+1;
}
cout<<ans+1<<endl;
return 0;
}