把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 027 解题报告

点此进入比赛

\(A\):Candy Distribution Again(点此看题面

  • \(n\)个小孩,第\(i\)个小孩当且仅当得到恰好\(a_i\)颗糖时才会满意。
  • 你有\(x\)颗糖,需要全部分给这\(n\)个小孩。
  • 问最多能让多少个小孩满意。
  • \(n\le100,1\le a_i,x\le10^9\)

贪心

显然先将\(a_i\)从小到大排序一下,然后贪心满足\(a_i\)较小的要求。

唯一要注意的就是若\(x\ge\sum_{i=1}^na_i\),需要判断一下\(x\)是否恰好等于\(\sum_{i=1}^na_i\)

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
using namespace std;
int n,m,a[N+5];
int main()
{
	RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i);sort(a+1,a+n+1);//读入+排序
	for(i=1;i<=n&&m>=a[i];++i) m-=a[i];return printf("%d\n",i<=n?i-1:(m?n-1:n)),0;//贪心,注意最后特判
}

\(B\):Garbage Collector(点此看题面

  • 一条数轴上有\(n\)个垃圾,分别位于\(0<x_1<x_2<...<x_n\le10^9\)
  • 从原点出发,每走到一个位置可以捡起该位置上的垃圾,你需要分若干批次把所有垃圾运回原点。
    • 当你拿着\(k\)个垃圾行走\(1\)个单位长度,会消耗\((k+1)^2\)的代价。
    • 当你捡起一个垃圾,或者将垃圾放在原点(无论多少个),都消耗\(X\)的代价(\(X\)为常数)。
  • 求最少花费多少的代价。
  • \(n\le2\times10^5\)

枚举搬运次数

考虑我们枚举搬运次数\(t\),那么最优的方案自然就是每隔\(t\)个垃圾就放在同一次中搬运。

容易发现,每相邻\(t\)个垃圾的贡献都是一样的,所以只需要枚举\(\lceil\frac n t\rceil\)次,总复杂度为调和级数。

据说运算过程可能会爆\(long\ long\),因此可以开__int128

代码:\(O(n\ln n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LL long long
using namespace std;
int n,m,a[N+5];LL s[N+5];
int main()
{
	RI i,t;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i),s[i]=s[i-1]+a[i];//s统计前缀和
	RI x,y;LL ans=1e18;__int128 res;for(t=1;t<=n;++t)
	{
		res=1LL*(n+t)*m+2*(s[n]-s[n-t]);//计算初始代价
		for(y=2,i=n;i>0;i-=t) x=max(i-t,0),res+=(2LL*y-1)*(s[i]-s[x]),++y;//每相邻t个代价相同
		res<ans&&(ans=res);
	}return printf("%lld\n",ans),0;
}

\(C\):ABland Yard(点此看题面

  • 给定一张\(n\)个点\(m\)条边的无向图,每个点有一个字符AB
  • 你可以从任何一个点出发,在图上任意行走,并将经过的点上的字符连成一个字符串。
  • 问是否能得到所有由AB组成的字符串。
  • \(n,m\le2\times10^5\)

找规律

首先,通过画图我们可以发现,合法的充要条件就是存在一个由若干\(A-A-B-B\)组成的环。

但只有这个条件依然是很难搞的。

考虑我们给边赋权,令相同字符之间边权为\(0\),不同字符之间边权为\(1\),显然就变成了寻找长度为\(4\)的倍数的\(0-1\)环。

然后我们发现,由于每有一个\(1\)字符就会变一次,因此环上必然有偶数个\(1\)。因此任意一个\(0-1\)环,长度必然为\(4\)的倍数!

综上,合法的充要条件就是存在一个\(0-1\)环。

拓扑

一个非常诡异的做法。

考虑进行一个类似于拓扑的过程,每次把所有边权相同的点删去。

如果最后还有点剩余,说明有解,否则无解。

证明可以自己画画图。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,++d[y][e[ee].op=v])
using namespace std;
int n,m,ee,lnk[N+5],vis[N+5],q[N+5],d[N+5][2];struct edge {int to,nxt,op;}e[2*N+5];char p[N+5];
int main()
{
	RI i,x,y;for(scanf("%d%d%s",&n,&m,p+1),i=1;i<=m;++i)
		scanf("%d%d",&x,&y),add(x,y,p[x]!=p[y]),add(y,x,p[x]!=p[y]);//建边
	RI H=1,T=0;for(i=1;i<=n;++i) (!d[i][0]||!d[i][1])&&(vis[q[++T]=i]=1);//初始化队列
	W(H<=T) for(i=lnk[q[H++]];i;i=e[i].nxt)//类似拓扑的过程
		!vis[e[i].to]&&!--d[e[i].to][e[i].op]&&(vis[q[++T]=e[i].to]=1);
	return puts(T^n?"Yes":"No"),0;//判断是否有点剩余
}

\(D\):Modulo Matrix(点此看题面

  • 给定一个正整数\(n\)
  • 要求构造一个\(n\times n\)的矩阵,满足下列条件:
    • 没有重复的元素,且所有元素均为不超过\(10^{15}\)的正整数。
    • 对于任意两个相邻的数\(x,y\),都满足\(max\{x,y\}\ mod\ min\{x,y\}\)等于\(m\)\(m\)可自己定)。
  • \(2\le n\le500\)

大致思路

显然\(m\)越小越好,因此直接取\(m=1\)

考虑我们必然是以一种交错的方式,对于所有\(i\)\(j\)奇偶性相同的\((i,j)\)根据某种策略填上一个数,对于所有\(i\)\(j\)奇偶性不同的\((i,j)\)直接令其等于四周数的\(lcm+1\)

这道题的关键是不能重复,因此我们要想办法找到一种填数方式使得\(lcm\)不会相同。

自然而然想到利用质数。

考虑我们给每条斜线(两种方向都包括)一个质数,然后对于每个\(i\)\(j\)奇偶性相同的\((i,j)\),都让它等于经过它的两条斜线上的质数之积。

然后发现每个\(i\)\(j\)奇偶性不同的\((i,j)\),应该是\(4\)个质数的乘积加\(1\),不会超出限制。

注意特判\(n=2\)

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
using namespace std;
int n,p[N+5],a[N+5],b[N+5];
I bool IsP(CI x) {for(RI i=2;i*i<=x;++i) if(!(x%i)) return 0;return 1;}
int main()
{
	RI i,j,m=0;for(scanf("%d",&n),i=2;m<2*n;++i) IsP(i)&&(p[++m]=i);//暴力找质数
	if(n==2) return puts("4 7\n23 10"),0;a[0]=a[n+1]=b[0]=b[n+1]=1;//特判n=2
	for(i=1;i<=n;++i) (i&1)?(a[i]=p[i]):(b[i]=p[i]);//分配质数,这里采取了一种大小交错的分发
	for(i=1;i<=n;++i) (i&1)?(b[i]=p[m-i+1]):(a[i]=p[m-i+1]);
	#define P1(x,y) ((x)+(y)>>1)//求出左下-右上的斜线编号
	#define P2(x,y) ((x)-(y)+n+1>>1)//求出左上-右下的斜线编号
	for(i=1;i<=n;++i,putchar('\n')) for(j=1;j<=n;++j)//枚举格子
		printf("%lld ",(i^j)&1?1LL*a[P1(i,j)]*b[P2(i,j)]:(1LL*a[P1(i-1,j)]*a[P1(i,j+1)]*b[P2(i-1,j)]*b[P2(i,j-1)]+1));//根据i与j奇偶性是否相同讨论
	return 0;
}

\(E\):ABBreviate(点此看题面

  • 给定一个由ab组成的字符串,可以任意进行下列两种操作:
    • 将一对连续的aa替换为b
    • 将一对连续的bb替换为a
  • 问能生成多少种不同的字符串。
  • 字符串长度\(\le10^5\)

神奇思路

一个非常神奇的思路。

不妨令\(a=1,b=2\),发现两种操作均不影响整个序列和模\(3\)的余数!

然后要证明一个结论:一个字符串\(s\)能变成一个字符\(x\)的充要条件是字符串\(s\)之和模\(3\)的余数与\(x\)对应的值相同\(s\)中有可变换的位置(即不是完全由ab重复构成)

证明的过程简单来说就是考虑如果我们转化到某一步让\(s\)无法再变换了,我们可以退回到上一步并改为合并另外一对位置,自己画画图就可以发现必然存在悔改的方案。

有了这个结论剩下的就非常容易了,直接\(DP\)即可。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,f[N+5],lst[3];char s[N+5];
int main()
{
	RI i,t=0;scanf("%s",s+1),n=strlen(s+1),f[0]=1,lst[1]=lst[2]=-1;
	for(i=1;i^n&&s[i]^s[i+1];++i);if(i==n) return puts("1"),0;//特判无可变换位置
	for(i=1;i<=n;++i) t=(t+(s[i]=='a'?1:2))%3,!t&&i^n&&(f[i]=1),//t记录前缀和,直接DP
		t=++t%3,~lst[t]&&Inc(f[i],f[lst[t]]),t=++t%3,~lst[t]&&Inc(f[i],f[lst[t]]),lst[t=++t%3]=i;
	return printf("%d\n",f[n]),0;//输出答案
}

\(F\):Grafting(点此看题面

  • 给定两棵\(n\)个节点的树\(A,B\),一开始\(A\)中的节点全是白色的。
  • 你可以进行任意次操作,每次选择一个白色的叶节点,断开它原先的边,随意给它连上一条新边,然后将它染黑。
  • 问最少通过几次操作,才能使\(A\)\(B\)相同(忽略颜色、考虑编号)。
  • 数据组数\(\le20\)\(n\le50\)

无根化有根

首先考虑到两棵无根树既不好判相同,也不好操作,所以我们要先把它变成有根树。

具体地,我们去枚举\((x,y)\)表示第一次操作将叶节点\(x\)\(y\)连边,然后令\(x\)成为树的根。

接下来就好操作了许多。

拓扑

对于树\(A\),除去与父节点连边正确的节点之外,显然每一个节点都必须在其子节点都被操作过之后操作。

对于树\(B\),同样除去正确的节点,每一个节点都必须在其子节点之前就先被操作。

(注意,还需要先判断如果某个错误的节点子树内存在正确的节点,必然无解)

容易发现这形成了一种拓扑关系,那么就可以建出一张图。

而我们的目的是找到一个合法的操作方案,也就是说只要这张图中不存在环就可以了。

这样一来这题就做完了。

代码:\(O(Tn^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50
#define add(x,y) (e[++ee].nxt=lnk[x],++d[e[lnk[x]=ee].to=y])
using namespace std;
int n,res,ans,f[N+5],g[N+5],d[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
struct Tree//存树
{
	int ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
	I void Clear() {ee=0,memset(lnk,0,sizeof(lnk));}//清空
	I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}//连边
	I int operator [] (CI x) Con {return lnk[x];}
	I edge operator () (CI x) Con {return e[x];}
}A,B;
I void dfsA(CI rt,CI x,CI lst)//以rt为根,遍历A,记下每个点的父亲
{
	f[x]=lst;for(RI i=A[x];i;i=A(i).nxt) A(i).to^lst&&A(i).to^rt&&(dfsA(rt,A(i).to,x),0);
}
I bool dfsB(CI x,CI lst,RI tg=1)//遍历B,判断每个点是否正确
{
	if(f[x]^lst) g[x]=tg=0,++res;else {if(!tg) return 0;g[x]=1;}//如果错误的节点子树内有正确的节点,直接返回
	for(RI i=B[x];i;i=B(i).nxt) if(B(i).to^lst&&!dfsB(B(i).to,x,tg)) return 0;return 1;
}
I void LinkA(CI rt,CI x,CI lst)//对于A建边
{
	!g[lst]&&add(x,lst);for(RI i=A[x];i;i=A(i).nxt) A(i).to^lst&&A(i).to^rt&&(LinkA(rt,A(i).to,x),0);//子节点连向父节点
}
I void LinkB(CI x,CI lst)//对于B建边
{
	lst&&!g[lst]&&add(lst,x);for(RI i=B[x];i;i=B(i).nxt) B(i).to^lst&&(LinkB(B(i).to,x),0);//父节点连向子节点
}
int q[N+5];I bool Topo(CI x,CI y)//拓扑
{
	RI i,H=1,T=0;for(ee=0,i=1;i<=n;++i) d[i]=lnk[i]=0;LinkA(x,y,x),LinkB(x,0);
	for(i=1;i<=n;++i) !d[i]&&(q[++T]=i);
	W(H<=T) for(i=lnk[q[H++]];i;i=e[i].nxt) !--d[e[i].to]&&(q[++T]=e[i].to);return T==n;//所有点都在序列中说明无环
}
int main()
{
	RI Tt,i,j,x,y;scanf("%d",&Tt);W(Tt--)
	{
		scanf("%d",&n),A.Clear(),B.Clear(),ans=n+1;
		for(i=1;i^n;++i) scanf("%d%d",&x,&y),A.Add(x,y),A.Add(y,x);
		for(i=1;i^n;++i) scanf("%d%d",&x,&y),B.Add(x,y),B.Add(y,x);
		for(i=1;i<=n;++i) if(!A(A[i]).nxt) for(f[i]=0,j=1;j<=n;++j)//枚举第一次操作
			i^j&&(res=A(A[i]).to!=j,dfsA(i,j,i),dfsB(i,0)&&res<ans&&Topo(i,j)&&(ans=res));//注意答案的初始化,判断这条边是否原本就存在
		printf("%d\n",ans>n?-1:ans);
	}return 0;
}
posted @ 2020-10-21 19:20  TheLostWeak  阅读(100)  评论(0编辑  收藏  举报