Atcoder AGC001 题解

A - BBQ Easy

排序,相邻两个一组。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
int a[105],n,ans;
int main()
{
	n=read();
	for(RI i=1;i<=n+n;++i) a[i]=read();
	sort(a+1,a+1+n+n);
	for(RI i=1;i<=n+n;i+=2) ans+=a[i];
	printf("%d\n",ans);
	return 0;
}

B - Mysterious Light

每次要做的事情就是将一个平行四边形切成边长为短边长度的若干等边三角形,直到切不出来位置,切不出来的时候,就出现了一个新的平行四边形,递归即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL n,x;
LL work(LL l1,LL l2) {
	if(!l2) return -l1;
	return work(l2,l1%l2)+2*(l1/l2)*l2;
}
int main()
{
	scanf("%lld%lld",&n,&x);
	printf("%lld\n",work(x,n-x)+n);
	return 0;
}

C - Shorten Diameter

我们知道,一棵树距一个点最远的点,一定是直径的两端点。所以说,所有点到直径中点的距离都不会超过 k 2 \frac{k}{2} 2k

对于 k k k为偶数的情况,枚举直径的中点,然后将到它距离大于 k 2 \frac{k}{2} 2k的点统统删除。

对于 k k k为奇数的情况,直径的中点在一条边上,我们枚举这条边,然后将到边的两端点距离大于 ⌊ k 2 ⌋ \lfloor \frac{k}{2} \rfloor 2k的点删除。

找最优解即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=2005,inf=0x3f3f3f3f;
int u[N],v[N],h[N],ne[N<<1],to[N<<1],dep[N];
int n,tot,ans,js,K;
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	for(RI i=h[x];i;i=ne[i])
		if(to[i]!=las) dep[to[i]]=dep[x]+1,dfs(to[i],x);
}
int main()
{
	n=read(),K=read();
	for(RI i=1;i<n;++i)
		u[i]=read(),v[i]=read(),add(u[i],v[i]),add(v[i],u[i]);
	ans=inf;
	if(K&1) {
		for(RI i=1;i<n;++i) {
			dep[u[i]]=dep[v[i]]=0,dfs(u[i],v[i]),dfs(v[i],u[i]),js=0;
			for(RI j=1;j<=n;++j) if(dep[j]>K/2) ++js;
			ans=min(ans,js);
		}
	}
	else {
		for(RI i=1;i<=n;++i) {
			dep[i]=0,dfs(i,0),js=0;
			for(RI j=1;j<=n;++j) if(dep[j]>K/2) ++js;
			ans=min(ans,js);
		}
	}
	printf("%d\n",ans);
	return 0;
}

D - Arrays and Palindrome

我们将能够保证相等的字符相连,可以发现,一段长度为偶数的回文串,每一个字符都连有一条边。一段为奇数的回文串,中心点没有连边,其他字符连了一条边。

所以每个字符的连边数只有可能是0,1,2

由于我们的目的是让整张图联通,所以整张图应该是一个环或者一条链。因此如果A中出现了3个及以上的奇数回文串,肯定是不可能连成联通图的,此时输出Impossible即可。

如果A中只有一个元素,B这么构造:前 ⌊ n 2 ⌋ + 1 \lfloor \frac{n}{2} \rfloor+1 2n+1分成一个回文串段,剩下的分成一段。分n模4的余数画图就可以发现这个构造没问题。

如果A中全是偶数长度回文串,B就分成第一个串的第一个元素到第二个串的第一个元素,第二个串的第二个元素到第三个串的第一个元素,第三个串的第二个元素到第四个串的第一个元素…第 m m m个串的第二个元素到第 n n n个元素,这么连,会形成以第一个串两中心元素之一和最后一个串两中心元素之一为端点的链。

这给了我们一点启迪。显然奇数长度回文串的中心应该为链的端点,所以我们把奇数长度回文串移到两边,然后按相同的方法构造即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
int n,m,js,a[105],kl[105],b[105];
int main()
{
	scanf("%d%d",&n,&m);
	for(RI i=1;i<=m;++i) {
		scanf("%d",&a[i]);
		if(a[i]&1) kl[++js]=i;
	}
	if(js>2) {puts("Impossible");return 0;}
	if(js>=1) swap(a[kl[1]],a[1]);
	if(js>=2) swap(a[kl[2]],a[m]);
	if(m==1) {
		printf("%d\n",a[1]);
		b[1]=n/2+1;
		if(n-b[1]) puts("2"),printf("%d %d\n",b[1],n-b[1]);
		else puts("1"),printf("%d\n",b[1]);
	}
	else {
		b[1]=a[1]+1;for(RI i=2;i<m;++i) b[i]=a[i];
		for(RI i=1;i<=m;++i) printf("%d ",a[i]);
		puts("");
		if(a[m]-1) {
			printf("%d\n",m);b[m]=a[m]-1;
			for(RI i=1;i<=m;++i) printf("%d ",b[i]);
		}
		else {
			printf("%d\n",m-1);
			for(RI i=1;i<=m-1;++i) printf("%d ",b[i]);
		}
		puts("");
	}
	return 0;
}

E - BBQ Hard

C a i + a j + b i + b j a i + a j C_{a_i+a_j+b_i+b_j}^{a_i+a_j} Cai+aj+bi+bjai+aj看作从 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)走到 ( a j , b j ) (a_j,b_j) (aj,bj),只能往上走或者往右走的方案数,那么原问题转化为求出以任意 ( − a i , − b i ) (-a_i,-b_i) (ai,bi)为起点任意 ( a i , b i ) (a_i,b_i) (ai,bi)为终点的方案数,再去掉一些重复计算的值即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int mod=1000000007,N=200005,bas=2000;
int n,ans;
int f[4005][4005],fac[8005],ni[8005],a[N],b[N];
int qm(int x) {return x>=mod?x-mod:x;}
int ksm(int x,int y) {
	int re=1;
	for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
	return re;
}
int C(int d,int u) {return 1LL*fac[d]*ni[u]%mod*ni[d-u]%mod;}
int main()
{
	n=read();
	for(RI i=1;i<=n;++i)
		a[i]=read(),b[i]=read(),++f[bas-a[i]][bas-b[i]];
	fac[0]=1;for(RI i=1;i<=8000;++i) fac[i]=1LL*fac[i-1]*i%mod;
	ni[8000]=ksm(fac[8000],mod-2);
	for(RI i=7999;i>=0;--i) ni[i]=1LL*ni[i+1]*(i+1)%mod;
	for(RI i=0;i<=bas+bas;++i)
		for(RI j=0;j<=bas+bas;++j) {
			if(i!=0) f[i][j]=qm(f[i][j]+f[i-1][j]);
			if(j!=0) f[i][j]=qm(f[i][j]+f[i][j-1]);
		}
	for(RI i=1;i<=n;++i) ans=qm(ans+f[a[i]+bas][b[i]+bas]);
	for(RI i=1;i<=n;++i) ans=qm(ans+mod-C(a[i]+b[i]+a[i]+b[i],a[i]+a[i]));
	printf("%lld\n",1LL*ans*ksm(2,mod-2)%mod);
    return 0;
}

F - Wide Swap

将原来的排列转化为一个 a p i = i a_{p_i}=i api=i的排列,原条件转化为相邻的两个元素如果差值超过 k k k就可以交换。那么差值不超过 k k k的两个元素的相对位置就固定了。 p p p的字典序最小等价于 a a a的字典序最小,所以用单调队列拓扑一下即可。

发现连边可以达到 O ( n 2 ) O(n^2) O(n2),这是因为有很多边重复了。对于元素 a i a_i ai,我们发现对于两个同小于/大于 a i a_i ai的元素,如果 a i a_i ai要向它们都连边是不必要的,因为它们的相对位置一定也靠连边确定了。所以用一个线段树来查询,每次找到值在 [ a i − k + 1 , a i − 1 ] [a_i-k+1,a_i-1] [aik+1,ai1] [ a i + 1 , a i + k − 1 ] [a_i+1,a_i+k-1] [ai+1,ai+k1]范围内,距 i i i最近的数连边即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=500005;
int n,K,tot,js;
int tr[N<<2],a[N],h[N],ne[N<<1],to[N<<1],du[N],ans[N];
priority_queue<int,vector<int>,greater<int> >q;
int query(int l,int r,int s,int t,int i) {
	if(l<=s&&t<=r) return tr[i];
	int mid=(s+t)>>1,re=0;
	if(l<=mid) re=query(l,r,s,mid,i<<1);
	if(mid+1<=r) re=max(re,query(l,r,mid+1,t,(i<<1)|1));
	return re;
}
void chan(int x,int s,int t,int i,int num) {
	if(s==t) {tr[i]=num;return;}
	int mid=(s+t)>>1;
	if(x<=mid) chan(x,s,mid,i<<1,num);
	else chan(x,mid+1,t,(i<<1)|1,num);
	tr[i]=max(tr[i<<1],tr[(i<<1)|1]);
}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,++du[y];}
void topsort() {
	for(RI i=1;i<=n;++i) if(!du[i]) q.push(i);
	while(!q.empty()) {
		int x=q.top();q.pop(),ans[x]=++js;
		for(RI i=h[x];i;i=ne[i]) {
			--du[to[i]];
			if(!du[to[i]]) q.push(to[i]);
		}
	}
}
int main()
{
	n=read(),K=read();
	for(RI i=1;i<=n;++i) a[read()]=i;
	for(RI i=1;i<=n;++i) {
		int k=query(max(1,a[i]-K+1),a[i],1,n,1);
		if(k) add(a[k],a[i]);
		k=query(a[i],min(n,a[i]+K-1),1,n,1);
		if(k) add(a[k],a[i]);
		chan(a[i],1,n,1,i);
	}
	topsort();
	for(RI i=1;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值