【AtCoder】AtCoder Grand Contest 039 解题报告

点此进入比赛

(A):Connection and Disconnection(点此看题面

大致题意: 给你一个字符串,将它重复(k)次。进行尽量少的操作,每次修改一个位置上的字符,使得不存在两个相邻位置上字符相同。求最少操作次数。

一个很(naive)的想法,就是将原串直接扫一遍,遇到与前一位相同的字符,就修改这一位字符为一个不存在的字符,并将计数器加(1)。最后将计数器乘上(k)

然后,比较此时串首与串尾是否相同(注意是此时,因为上面操作中可能会修改串尾),如果相同,那么对于这(k)次重复的(k-1)个首尾交点,我们都需要修改,因此将计数器再加上(k-1)

如果仅仅这样交上去,会(WA)掉两个点。

但只要再仔细推一推,就可以找到刚才想法的漏洞。

首先,我们需要特判所有字符相同的情况,此时答案就是(lfloorfrac{|S| imes k}2 floor)然后你就发现你能多过一个点。

其次,我们需要将原串倒着再执行一遍上述操作,然后将正着和倒着两种情况下的答案取(min)。其实也就相当于一种是尽量取串尾,一种是尽量取串头,这可能会影响到首尾交点处是否需要修改。

这样一来,就能过了此题。

#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;
string s,p;int n,k;
I bool Check() {for(RI i=1;i^n;++i) if(s[i]^s[i-1]) return false;return true;}//检查是否所有字符相同
int main()
{
	RI i;long long t1=0,t2=0;if(cin>>s>>k,n=s.length(),Check()) return printf("%lld",(1LL*n*k)>>1),0;//特判所有字符相同
	for(p=s,i=1;i^n;++i) s[i]==s[i-1]&&(s[i]=0,++t1);t1*=k,s[0]==s[n-1]&&(t1+=k-1);//正着求答案
	for(s=p,i=n-2;~i;--i) s[i]==s[i+1]&&(s[i]=0,++t2);t2*=k,s[0]==s[n-1]&&(t2+=k-1);//倒着求答案
	return printf("%lld",min(t1,t2)),0;//输出答案
}

(B):Graph Partition(点此看题面

大致题意: 一张无向连通图,让你把点集分到若干个集合中,使得对于图上任意一条边,满足其连接的两个节点在相邻的两个集合中。求最多能分成多少个集合。

数据范围很小......

因此,我们可以直接枚举一个起点,然后(BFS)求出答案。

这一过程应该还是比较简单的,直接上代码了吧。

#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 200
using namespace std;
int n,p[N+5],q[N+5],a[N+5][N+5];
I int BFS(CI x)//BFS求答案
{
	for(RI i=1;i<=n;++i) p[i]=0;//清空
	RI i,k,H=1,T=1;p[q[1]=x]=1;W(H<=T) for(k=q[H++],i=1;i<=n;++i)
		if(a[k][i]) {if(p[i]&&p[i]^(p[k]-1)&&p[i]^(p[k]+1)) return -1;!p[i]&&(p[q[++T]=i]=p[k]+1);}//若矛盾返回-1,否则赋值并加入队列
	return p[q[T]];//返回答案
}
int main()
{
	RI i,j,t,ans=-1;string s;for(scanf("%d",&n),i=1;i<=n;++i)
		for(cin>>s,j=1;j<=n;++j) s[j-1]&1&&(a[i][j]=1);
	for(i=1;i<=n;++i) ans<(t=BFS(i))&&(ans=t);return printf("%d",ans),0;//枚举起点输出答案
}

(C):Division by Two with Something(点此看题面

大致题意: 给你一个(n)位二进制数(x),求出对于(0sim x)的每一个(k),进行奇数去末位(1)在首位加(0)、偶数去末位(0)在首位加(1)的操作后,第一次变回(k)本身的操作次数之和。

首先,不难发现,无论什么数,在(2n)次操作后,都必然会变回自身。

否则,考虑一个二进制数(t),它第一次变回自身的操作次数。

我们将(t)复制一遍并取反,然后放在它的前面,那么,进行(p)((ple n))操作后,其实就是这个串中的([n-p+1,2n-p])一段。

由于一个数不可能通过奇数次变回自身,所以我们假设进行(2i)次操作能使(t)变回自身,又因进行(2n)次操作后(t)必然能变回自身,所以我们可以确定,(i|n)

则,我们可以把(t)划分成长度为(i)(frac ni)段,就能发现其中奇数段之间相等,偶数段之间相等,奇数段与偶数段是互相取反的结果。注意,如果(frac ni)为偶数,就说明(t=sim t),这显然是不可能的,因此(frac ni)必然为奇数。

也就是说,我们只要枚举(n)的一个因数(i),满足(frac ni)为奇数,那么以任意一段合法的长度为(i)的二进制数作为开头,都必然存在唯一一个数使其满足经过(2i)次操作能变回自身。而此处的合法,只要满足字典序小于等于题目中给出的(x)即可。

但要注意,这里求出的经过(2i)次操作能变回自身的数不一定是经过(2i)次操作后第一次变回自身,因此还需要通过容斥来求出真正的答案。

#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 X 998244353
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n;int a[N+5],b[N+5],f[N+5],g[N+5];
int main()
{
	RI i,j,p,k=0,t=0,cnt=0;string s;
	for(scanf("%d",&n),cin>>s,i=1;i<=n;++i) a[i]=s[i-1]&1;//读入
	for(i=1;i<=n;++i) if(!(n%i)&&(n/i)&1)//枚举一个符合条件的i
	{
		for(f[++k]=i,j=1;j<=i;++j) g[k]=(g[k]<<1|a[j])%X;++g[k];//计算合法数的个数上界,只需判断是否能够达到上界
		for(j=1;j<=i;++j) b[j]=a[j];
		for(j=i+1;j<=n;++j) if(b[j]=b[j-i]^1,a[j]^b[j]) {a[j]<b[j]&&--g[k];break;}//若超出范围,则个数无法达到上界,减1
	}
	for(i=1;i<=k;++i) for(j=i+1;j<=k;++j) !(f[j]%f[i])&&Inc(g[j],X-g[i]);//容斥
	for(i=1;i<=k;++i) t=(2LL*f[i]*g[i]+t)%X,Inc(cnt,g[i]);//统计答案
	return printf("%d",t),0;//输出答案
}

(D):Incenters(点此看题面

大致题意: 给你圆上(n)个点(以角度的形式给出),求任选其中三个点后这三点构成三角形的内心坐标的平均值。

几何画板是个好东西......

按照题意,假设(A,B,C)三点是我们选出的点,连结(AB,AC,BC),就得到如图所示的红色三角形。

然后,作出( riangle ABC)三个顶点的角平分线并延长分别交(⊙ABC)于点(A',B',C'),三条角平分线交于点(P),点(P)即为所求的内心。

连结(A'B',A'C',B'C'),就得到如图所示的紫色三角形。

通过几何画板,我们可以发现两个至关重要的结论:

结论一:(C')是弧(AB)的中点。

(ecause CC')平分(ang ACB, hereforeang ACC'=ang BCC', herefore)(AC'=)(BC', herefore C')是弧(AB)的中点(.)

结论二:(P)( riangle A'B'C')的垂心。

连结(B'O)并延长,分别交(AC,CC',⊙O)于点(E,N,D.)

(ecause B')是弧(AC)的中点(, herefore B'D⊥AC, herefore ang CEN=90)(.)

(ecause A')是弧(BC)的中点(,B')是弧(AC)的中点(,C')是弧(AB)的中点(, herefore)(AC'=frac12)(AB,)(B'C=frac12)(AC,)(A'C=frac 12)(BC, herefore)(AC'+)(B'C+)(A'C)的度数为(180)(.)

(ecause B'D)是直径(, herefore)(B'C+)(A'C+)(A'D)的度数为(180)(, herefore)(AC'=)(A'D, hereforeang ACC'=ang A'B'D.)

(ecause CC')平分(ang ACB, herefore ang BCC'=ang ACC', hereforeang BCC'=ang A'B'D.)

(ecauseang CKM=ang B'KN, hereforeang CMK=ang B'NK.)

( herefore ang CKM=ang CEN=90)(, herefore CC'⊥A'B'.)

同理,最终可得,(P)( riangle A'B'C')的垂心。

由结论二,我们作出( riangle A'B'C')的三条中线(图中三条黄色线段),其交点(Q)即为( riangle A'B'C')的重心。

又因为(⊙O)( riangle A'B'C')的外接圆,所以点(O)( riangle A'B'C')的外心。

有了外心、重心、垂心,根据欧拉线定理,(O,P,Q)三点共线,(PQ=2OQ)。所以(OP=3OQ),即点(P)的横纵坐标都是点(Q)的三倍。

而重心(Q)的坐标,实际上就是( riangle A'B'C')三点坐标的平均值。

综合结论一,我们可以(O(n^2))枚举圆上两点(A,B)

那么当点(C)(AB)优弧上时,(C')就是(AB)劣弧的中点,当点(C)(AB)劣弧上时,(C')就是(AB)优弧的中点。

(AB)优弧上有(x)个点,劣弧上有(y)个点,那么(C')的坐标期望就是(AB)劣弧中点坐标乘(frac x{x+y})(AB)优弧中点坐标乘(frac y{x+y}),其中显然(x+y=n-2)

由于题中给出的是点的角度,因此弧中点坐标就是角度的平均值,这一点还是很良心的。

这样一来,我们就求出当选择(AB)作为红色三角形一边时,紫色三角形一点的坐标期望。

(AB)作为红色三角形一边的概率是(frac 1{C_n^2}=frac 2{n(n-1)}),因此将所有的(A,B)求出的坐标期望求和并乘上(frac 2{n(n-1)}),就是最后紫色三角形重心的坐标期望。

然后乘(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 3000
#define DB double
using namespace std;
int n,l,a[N+5];const DB pi=acos(-1);
struct Point
{
	DB x,y;I Point() {x=y=0;}I Point(Con DB& a):x(cos(2*pi*a/l)),y(sin(2*pi*a/l)){}//根据角度算坐标
	I Point(Con DB& a,Con DB& b):x(a),y(b){}
	I Point operator + (Con Point& t) Con {return Point(x+t.x,y+t.y);}
	I Point operator * (Con DB& t) Con {return Point(x*t,y*t);}
}ans;
int main()
{
	RI i,j;for(scanf("%d%d",&n,&l),i=1;i<=n;++i) scanf("%d",a+i);//读入角度
	for(sort(a+1,a+n+1),i=1;i<=n;++i) for(j=i+1;j<=n;++j)//枚举两个点
		ans=ans+Point((a[i]+a[j])/2.0)*(1.0*(n-2-(j-i-1)*2)/(n-2));//统计
	return ans=ans*(6.0/n/(n-1)),printf("%.10lf %.10lf",ans.x,ans.y),0;//计算并输出答案
}

(E):Pairing Points(点此看题面

大致题意: 一个圆上有(2n)个点,有若干对点之间可以连边。求有多少种连边方案,使得每个点恰好连出一条边,并满足边之间连通且无环。

我们定义一个基本局面((l,r,mid)(l<mid<r))表示满足以下条件的一个局面:

  • ([l,mid-1],[mid+1,r])中的点都还未连边,且只会跟([l,mid-1],[mid+1,r])中的点连边。
  • (mid)已和([l,r])外的一点连边。

考虑一开始,先对于(1)号点,为其枚举一个(x)号点,表示(1)(x)相连,那么我们就得到一个局面((2,2n,x))

可以发现,对于某一局面,若([l,mid-1])中的一点要和([mid+1,r])中的一点相连,就必然与以(mid)为一端的线段有交。而如果与(mid)的线段有交的某两条线段再相交,就会有环。因此,这样的一组线段它们的端点应该是单调的。

所以,我们可以枚举最靠外的一条与(mid)的线段有交的线段(i,j),其中(i∈[l,mid-1],j∈[mid+1,r])

考虑对于([i+1,mid-1])中的一点,它如果要向([i+1,mid-1])这一范围之外的点连边,必然会经过(i)的线段或(mid)的线段中的一条。由于不能存在环,则必然是以一个点(p)为界,其中([i+1,p])中的点向外连边时与(i)的线段有交,([p+1,mid-1])中的点向外连边时与(mid)的线段有交。

同理,在([mid+1,j-1])中,我们也能找到符合类似定义的一点(q)

然后我们就发现,对于((l,p,i),(p+1,q-1,mid),(q,r,j))这三个局面,它们都符合上面对于基本局面的定义。

因此,我们只要继续递归处理对应局面即可。

#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 20
#define LL long long
using namespace std;
int n,a[2*N+5][2*N+5];LL f[2*N+5][2*N+5][2*N+5];
I LL DP(CI l,CI r,CI mid)//处理某一局面
{
	if(~f[l][r][mid]) return f[l][r][mid];if(l==r) return 1;if(l==mid||r==mid) return 0;//判边界
	RI i,j,p,q;LL t=0;for(i=l;i^mid;++i) for(j=r;j^mid;--j) if(a[i][j])//枚举最靠外的边
		for(p=i;p^mid;++p) for(q=j;q^mid;--q) t+=DP(l,p,i)*DP(p+1,q-1,mid)*DP(q,r,j);//枚举两个分界点,递归求答案
	return f[l][r][mid]=t;//记忆化,返回答案
}
int main()
{
	RI i,j;string s;memset(f,-1,sizeof(f));
	for(scanf("%d",&n),i=1;i<=2*n;++i) for(cin>>s,j=1;j<=2*n;++j) a[i][j]=s[j-1]&1;//读入
	LL t=0;for(i=2;i<=2*n;++i) a[1][i]&&(t+=DP(2,2*n,i));return printf("%lld",t),0;//枚举与1相对的点,统计答案并输出
}

(F):Min Product Sum(占坑待填)

(F)题显然不可做,看题解看了半个小时也没看懂。

原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC039.html