AtCoder Grand Contest 004
A - Divide a Cuboid
翻译
给定一个\(A*B*C\)的立方体,现在要把它分成两个立方体,求出他们的最小体积差。
题解
如果有一条边是偶数显然可以均分,否分沿着最长边隔开。
#include<iostream>
using namespace std;
int a,b,c;
int main()
{
cin>>a>>b>>c;
if(a%2==0||b%2==0||c%2==0)
cout<<0<<endl;
else
cout<<min(1ll*a*b,min(1ll*a*c,1ll*b*c))<<endl;
return 0;
}
B - Colorful Slimes
翻译
有\(n\)个数字,得到第\(i\)个需要花费\(a_i\)的代价。还可以把所有数字循环向后移一位,即\(i\)变成\(i+1\),\(n\)变成\(1\),花费的代价为\(x\)。求得到所有数字的最小代价。
题解
枚举一下循环右移的次数,那么等价于每个数字可以在一定的范围内选取,选择最小值即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
#define ll long long
#define MAX 2020
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,K,a[MAX];
int mn[MAX];
ll ans=1e18;
int main()
{
n=read();K=read();memset(mn,63,sizeof(mn));
for(int i=0;i<n;++i)a[i]=read();
for(int i=0;i<=n;++i)
{
for(int j=0;j<n;++j)
mn[j]=min(mn[j],a[(j-i+n)%n]);
ll ret=0;
for(int j=0;j<n;++j)ret+=mn[j];
ans=min(ans,ret+1ll*K*i);
}
cout<<ans<<endl;
return 0;
}
C - AND Grid
翻译
给定一个网格图,有些位置已经被涂色。要求构造两个相同大小的网格图,并且在上面涂色,需要保证颜色四联通。满足这两个网格的涂色部分的重合位置恰好是给定的网格图的涂色位置。
题解
似乎感觉这题有人讲过的样子?
一道很妙的构造题。首先把所有的要求涂色的位置全部涂上,然后考虑如何让他们联通。
一个图把所有奇数行除最左和最右两列之外的所有位置全部涂上,再涂上第一列。
另外一个把所有偶数行除最左最右外的所有位置全部涂上,再涂上最后一列。
这样就连起来了。
#include<cstdio>
#include<cstring>
#define MAX 505
int h,w;
char g[MAX][MAX],A[MAX][MAX],B[MAX][MAX];
int main()
{
scanf("%d%d",&h,&w);
for(int i=1;i<=h;++i)scanf("%s",g[i]+1);
memset(A,'.',sizeof(A));memset(B,'.',sizeof(B));
for(int i=1;i<=h;++i)
for(int j=1;j<=w;++j)
if(g[i][j]=='#')A[i][j]=B[i][j]='#';
for(int i=1;i<=h;++i)
for(int j=2;j<w;++j)
(i&1)?A[i][j]='#':B[i][j]='#';
for(int i=1;i<=h;++i)A[i][1]=B[i][w]='#';
for(int i=1;i<=h;++i,puts(""))
for(int j=1;j<=w;++j)
putchar(A[i][j]);
puts("");
for(int i=1;i<=h;++i,puts(""))
for(int j=1;j<=w;++j)
putchar(B[i][j]);
return 0;
}
D - Teleporter
翻译
有\(n\)个城市,每个城市有一个传送点,都可以传送到唯一的另外一个城市,保证从任何位置出发经过若干次传送之后能够到达\(1\)号城市。现在希望修改一些点的目的地,使得从任何一点出发在传送\(K\)次之后恰好都能到达\(1\)号城市,求最少要改变目的地的城市的数量。
题解
首先\(1\)号点一定在一个大小为\(K\)的因数的一个环内。那么抛去这个环,剩下的所有位置一定是一个树型结构。然而因为从\(1\)出发经过\(K\)的时间之后恰好能够达到\(1\),那么从环上其他点出发必定不能到达\(1\),所以无论如何\(1\)的边恰好指向自己。那么问题等价于了给定一棵树,问最少改变几个点使得任意一点到达\(1\)的最短距离都小于等于\(K\)。那么一旦子树内的最深深度大于\(K\)了,就把当前点断开就好了。
为啥我觉得这题比BC两题容易些啊
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#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;
}
struct Line{int v,next;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,K,ans,fa[MAX],dep[MAX];
int dfs(int u,int dep)
{
int ret=dep;
for(int i=h[u];i;i=e[i].next)
ret=max(ret,dfs(e[i].v,dep+1));
if(fa[u]!=1&&ret-dep==K-1){++ans;return 0;}
return ret;
}
int main()
{
n=read();K=read();
for(int i=1;i<=n;++i)fa[i]=read();
for(int i=2;i<=n;++i)Add(fa[i],i);
if(fa[1]!=1)++ans;fa[1]=1;
dfs(1,0);
printf("%d\n",ans);
return 0;
}
E - Salvage Robots
翻译
有一个棋盘,上面要么是空的,要么有一个机器人,要么是一个出口。每次可以命令所有机器人向上下左右中的某个方向移动一格,如果它超出了棋盘的边界就会消失。如果它到了出口的位置就会被你救下(并且从棋盘上消失)。求你能够救下的机器人的最大值。
题解
好神仙啊。换种思路,不动机器人,动棋盘,统计一下答案就好了。
设\(f[a][b][c][d]\)表示出口动的范围是\(((a,b),(c,d))\)构成的矩形。然后其他的东西自己稍微看看题解吧,写下来太麻烦了。大概就是如果知道了出口移动的范围的话,那么就可以圈定出哪一些机器人会在这个范围内被选走。如果要拓展一行或者一列的话,意味着放弃了相反方向的一行或者一列,所以就这样子转移一下就好了。太神仙了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define si short int
#define MAX 101
si f[MAX][MAX][MAX][MAX],s[MAX][MAX];
si sum(int a,int b,int c,int d)
{
if(a>c)swap(a,c);if(b>d)swap(b,d);
return s[c][d]-s[a-1][d]-s[c][b-1]+s[a-1][b-1];
}
si n,m,X,Y,ans;
char g[MAX][MAX];
void upd(si &x,si y){if(x<y)x=y;}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)scanf("%s",g[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(g[i][j]=='E')X=i,Y=j;
else if(g[i][j]=='o')s[i][j]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
for(int a=X;a;--a)
for(int b=Y;b;--b)
for(int c=X;c<=n;++c)
for(int d=Y;d<=m;++d)
{
ans=max(ans,f[a][b][c][d]);
int L=d-Y+1,R=m-Y+b,U=c-X+1,D=n-X+a;
if(a>U)upd(f[a-1][b][c][d],f[a][b][c][d]+sum(a-1,max(b,L),a-1,min(d,R)));
if(b>L)upd(f[a][b-1][c][d],f[a][b][c][d]+sum(max(a,U),b-1,min(c,D),b-1));
if(c<D)upd(f[a][b][c+1][d],f[a][b][c][d]+sum(c+1,max(b,L),c+1,min(d,R)));
if(d<R)upd(f[a][b][c][d+1],f[a][b][c][d]+sum(max(a,U),d+1,min(c,D),d+1));
}
cout<<ans<<endl;
return 0;
}
F - Namori
翻译
给定一个基环树(或者树),每次可以将相邻的两个同色节点同时反色。初始状态时所有节点都是白色,问能够将所有节点都变成黑色,如果可以,输出最小步数。
题解
既然给了部分分,那么先考虑树的情况。
一棵树:树是一个二分图,看起来和这里每次选择相邻的两个节点很有关系的样子。既然每次可以选择相邻的两个点,然后把他们的颜色取反。我们就认为可以是所有深度为奇数的点一开始的颜色是白,所有深度为偶数的点一开始的颜色为黑,每次可以选择一对有边相连的点,然后交换他们的颜色,现在的目的就是让所有奇数点变成黑色,所有白色点变成偶数。很明显,这样题目还是一样的,但是更加好分析了。既然每次交换两个颜色,那么当且仅当黑色个数和白色个数是一样的时候才有解。我们把所有黑点标记为\(1\),所有白点标记为\(-1\),设\(s_i\)表示以\(i\)为根的子树和。假设\(s_i\)为正数,那么意味着黑色有多,那么我们要把这些黑色移出去,否则,意味着黑色少了,要从别的地方移黑色进来,那么显然这个的和就是最小步数了。
然后部分分就到手了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 100100
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;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,m,dep[MAX],s[MAX],tot,ans;
void dfs(int u,int ff)
{
dep[u]=dep[ff]+1;s[u]=(dep[u]&1)?1:-1;tot+=s[u];
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=ff)dfs(e[i].v,u),s[u]+=s[e[i].v];
ans+=abs(s[u]);
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int a=read(),b=read();
Add(a,b);Add(b,a);
}
if(m==n-1)
{
dfs(1,0);
if(tot!=0)puts("-1");
else printf("%d\n",ans);
}
return 0;
}
接下来我们考虑存在环的问题。
有了树的经验,我们尽量把这个东西也往二分图上面靠,那么发现需要分成奇环和偶环来考虑。
首先考虑奇环:我们断掉一条边,形成了一棵树,因为是奇环,所以一定是在同侧点上面连接的,不妨假设连在了两个黑点上。我们考虑一下这条边可以干什么呢?把他们同时从黑色变成白色,或者同时从白色变成黑色。在结合一下上面那个树的做法的意义,我们可以理解为,这两个点可以无限吃掉黑色格子或者无限生产黑色格子。那么现在如果黑白两色的差恰好是偶数的话,那么我们就可以无限生产或者吃掉黑色格子,达到平衡的目的,那么显然答案就是假装这条边不存在时,但是经过了这条边的平衡后的每个点的子树和作为答案,再加上这条边需要生产或者吃掉黑色格子的次数,即黑白格子数之差除以二。
考虑偶环,还是断开一条边形成一棵树,那么多出来的边连在了两个异侧点上。意味着我们可以通过这条边来运输黑格子。假设这条边一共运输了\(x\)次(可以小于\(0\)),那么等价于改变了子树的黑白格子作为\(1\)和\(-1\)时的和。但是改变的数量显然是固定的,因此我们可以用\(x\)作为未知数,把所有的点全部表示出来,那么所有的\(s_i=k*x+p\),显然\(k\in\{-1,0,1\}\),而最终的贡献是\(abs(x)+\sum abs(s_i)\),既然有\(abs\),所以\(k=1\)或\(-1\)是等价的,而\(k=0\)的贡献是固定的,因此,等价于我们有若干\(abs(x-p_i)\)的形式,求他们的最小和,这个东西就是中位数的模板了。
总结一句,这题太太太太太神仙了吧,不是我这种菜鸡会做的题目。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 100100
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;
}
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,m,dep[MAX],s[MAX],k[MAX],ln,U,V,S[MAX],top,tot;
ll ans;
void dfs(int u,int ff)
{
dep[u]=dep[ff]+1;s[u]=(dep[u]&1)?1:-1;tot+=s[u];
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
if(dep[v])ln=abs(dep[u]-dep[v])+1,U=u,V=v;
else dfs(v,u);
}
}
void dfs2(int u,int ff)
{
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==ff||(U==u&&V==v)||(U==v&&V==u))continue;
dfs2(v,u),k[u]+=k[v],s[u]+=s[v];
}
if(k[u]==0)ans+=abs(s[u]);
else S[++top]=k[u]*s[u];
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int a=read(),b=read();
Add(a,b);Add(b,a);
}
dfs(1,0);
if(m==n-1){if(tot!=0){puts("-1");return 0;}}
else if(ln&1)
{
if(tot%2==1){puts("-1");return 0;}
ans+=abs(tot/2);s[U]-=tot/2,s[V]-=tot/2;
}
else
{
if(tot!=0){puts("-1");return 0;}
k[U]=1;k[V]=-1;
}
dfs2(1,0);
S[++top]=0;sort(&S[1],&S[top+1]);
for(int i=1;i<=top;++i)ans+=abs(S[(top+1)/2]-S[i]);
cout<<ans<<endl;
return 0;
}