AtCoder Grand Contest 001

AtCoder Grand Contest 001

A - BBQ Easy

翻译

给你\(2n\)个数,需要两两配对成\(n\)对,每对的权值定义为两个数的较小值,求最大权值和。

题解

排序即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 202
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 ans,n,a[MAX<<1];
int main()
{
	n=read();
	for(int i=1;i<=n+n;++i)a[i]=read();
	sort(&a[1],&a[n+n+1]);
	for(int i=1;i<=n+n;i+=2)ans+=a[i];
	printf("%d\n",ans);
	return 0;
}

B - Mysterious Light

翻译

见洛谷

题解

大概画一个图,发现其实每次都等价于把一个\(60°\)平行四边形分解成若干个变成等于短边的等边三角形。
如果多出来了一部分,发现在干的事情是等价的,所以直接递归做就行了。
我交了几遍一直只有部分分,发现递归的函数带的参定义为了\(int\)。以后这种问题还是要注意。

#include<iostream>
using namespace std;
#define ll long long
ll n,x,ans;
ll calc(ll n,ll x)
{
	if(!x)return -n;
	ll d=n%x;
	return calc(x,d)+(n/x)*2*x;
}
int main()
{
	cin>>n>>x;ans=n;
	ans+=calc(n-x,x);
	cout<<ans<<endl;
	return 0;
}

C - Shorten Diameter

翻译

见洛谷

题解

为什么这么傻逼的题目我都不会做。。。果然菜的不行啊。
一个简单的想法就是我们钦定一个点作为根节点,然后删掉所有深度大于\(K/2\)的点。
如果\(K\)是奇数的时候就不能直接这么做,我们就钦定一条边,然后把这条边连接的两个点当成两棵子树,同样不准有点的深度大于\(K/2\)就好了。
时间复杂度\(O(n^2)\),为什么我就不会做呢?我现在真是蠢得不行啊。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define MAX 2002
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,K,dep[MAX],tot,ans=1e9;
void dfs(int u,int ff)
{
	dep[u]=dep[ff]+1;
	if(dep[u]>K/2)++tot;
	for(int i=h[u];i;i=e[i].next)
		if(e[i].v!=ff)dfs(e[i].v,u);
}
int main()
{
	n=read();K=read();
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read();
		Add(u,v);Add(v,u);
	}
	dep[0]=-1;
	if(K%2==0)
		for(int i=1;i<=n;++i)tot=0,dfs(i,0),ans=min(ans,tot);
	else
		for(int u=1;u<=n;++u)
			for(int i=h[u];i;i=e[i].next)
				dep[e[i].v]=-1,tot=0,dfs(u,e[i].v),dep[u]=-1,dfs(e[i].v,u),ans=min(ans,tot);
	printf("%d\n",ans);
	return 0;
}

D - Arrays and Palindrome

翻译

有两个和为\(N\)的数列\(\{a\},\{b\}\)
对于任意一个满足以下两个条件的长度为\(N\)的串\(S\)

  • \(a_1\)个字符组成的串是回文串,接下来的\(a_2\)个字符组成的串是回文串,接下来\(a_3\)个......
  • \(b_1\)个字符组成的串是回文串,接下来的\(b_2\)个字符组成的串是回文串,接下来\(b_3\)个......
    都满足\(S\)的所有字符都相等。
    给定一个长度为\(M\)的数列\(A\),并且已知\(a\)\(A\)的一个排列。构造一个满足条件的\(b\)

题解

如果只考虑其中一个数列,那么能够得到的信息是一系列的相等关系,那么,再通过错位的相等显然就可以得到一系列的连等关系。换种说法,就是把所有相等关系连起来,那么这些变恰好能够让你一笔画。
一笔画的条件就很好判断了,奇度点的个数不能超过\(2\),什么情况下会出现奇度点?一个点会被少连一次当且仅当恰好在某一个奇数回文串的正中间。而如果一个点在两个序列的限制条件中都是自己连向自己的话,显然不联通从而无解。
那么就很好办了,只需要把\(A\)中的奇数段找出来,数下个数,如果合法,一个丢前面一个丢后面,然后构造\(B\)的时候只需要让他们错位就好了。
可以看看题解里面画的图。

#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;
}
int a[MAX],n,m,S[MAX],top;
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;++i)
	{
		a[i]=read();
		if(a[i]&1)S[++top]=i;
	}
	if(top>2){puts("Impossible");return 0;}
	if(top)swap(a[1],a[S[1]]);
	if(top>1)swap(a[m],a[S[2]]);
	if(m==1)
	{
		if(a[1]==1)printf("1\n1\n1\n");
		else printf("%d\n2\n%d 1\n",a[1],a[1]-1);
		return 0;
	}
	for(int i=1;i<=m;++i)printf("%d ",a[i]);puts("");
	printf("%d\n",m-(a[m]==1));
	printf("%d ",a[1]+1);
	for(int i=2;i<m;++i)printf("%d ",a[i]);
	if(a[m]>1)printf("%d ",a[m]-1);
	puts("");return 0;
}

E - BBQ Hard

翻译

洛谷

翻译其实有点问题。

应该是

\[\sum_{i=1}^n\sum_{j=i+1}^nC_{a[i]+b[i]+a[j]+b[j]}^{a[i]+a[j]} \]

题解

这题可以说非常妙了。

我们可以把这个值看做在网格图上的一点\((-a[i],-b[i])\)走到\((a[j],b[j])\)的方案数。
而网格图走的方案数可以直接递推得到。
那么我们对于每个点把它的坐标取反到第三象限,然后对于整个坐标系计算走到每一个格子的总方案。
把所有\((a[i],b[i])\)的答案累加,再减去自己到自己的方案数,最后除二就是答案了。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 200200
#define MOD 1000000007
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
const int py=2010;
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 a[MAX],b[MAX],n,ans;
int f[4500][4500];
int inv[9000],jc[9000],jv[9000];
int C(int n,int m){return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read(),b[i]=read();
	for(int i=1;i<=n;++i)f[py-a[i]][py-b[i]]+=1;
	for(int i=1;i<=py*2;++i)
		for(int j=1;j<=py*2;++j)
			add(f[i][j],f[i-1][j]),add(f[i][j],f[i][j-1]);
	inv[0]=inv[1]=jc[0]=jv[0]=1;
	for(int i=1;i<py<<2;++i)jc[i]=1ll*jc[i-1]*i%MOD;
	for(int i=2;i<py<<2;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<py<<2;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
	for(int i=1;i<=n;++i)add(ans,f[a[i]+py][b[i]+py]);
	for(int i=1;i<=n;++i)add(ans,MOD-C(2*(a[i]+b[i]),2*a[i]));
	ans=1ll*ans*inv[2]%MOD;printf("%d\n",ans);
	return 0;
}

F - Wide Swap

翻译

有一个长度为\(n\)的排列\(P\),对于满足\(|i-j|\ge K\)\(i,j\),如果\(|P_i-P_j|=1\),那么可以交换\(P_i,P_j\)

求可能的最小字典序排列。

题解

神仙题我都只会看题解.jpg

发现\(K\)是一个很蛋疼的东西,于是转化一下(反正我不会.jpg),令\(a_{P_i}=i\),得到了一个排列\(a_i\)。这样子问题等价于变成了,每次可以交换相邻两个位置,并且他们的差的绝对值要大于等于\(K\)。这样子性质优秀很多,首先我们自己yy一下,认为\(P\)的字典序要最小,等价于\(a\)的字典序要最小(似乎字典序最小和最小的数尽可能在前面是一样的?)。接着再考虑一下\(a\)的交换关系,如果两个数不能交换,那么他们两个的相对位置永远不会变,当且这个数和后面的所有数的相对位置也不可能改变了。相对位置确定了,如果没有确定的显然就是没有限制,那么一遍拓扑排序就可以搞定。

然而这样子的复杂度在最坏情况下边数是\(O(n^2)\)的。大概是多了些什么边呢?比如说\(x,y,z\)三个数,\(,x\rightarrow y,x\rightarrow z,y\rightarrow z\),那么显然\(x\rightarrow z\)边是没有意义的。显然,对于任意一个\(a_i\),我们连边的范围一定是\([a_{i-k},a_{i+k}]\)之间的,上面那个式子告诉我们,显然只需要连向最近的一个点就可以了(比如之前那个连边,显然顺序是\(x-y-z\)),这样子用一个线段树找最近的位置就好了?

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 500500
#define lson (now<<1)
#define rson (now<<1|1)
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;
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[v]++;}
int a[MAX],P[MAX],ans[MAX],tot;
int t[MAX<<2];
priority_queue<int,vector<int>,greater<int> >Q;
void Modify(int now,int l,int r,int p,int w)
{
	if(l==r){t[now]=w;return;}
	int mid=(l+r)>>1;
	if(p<=mid)Modify(lson,l,mid,p,w);
	else Modify(rson,mid+1,r,p,w);
	t[now]=min(t[lson],t[rson]);
}
int Query(int now,int l,int r,int L,int R)
{
	if(L>R)return t[0];
	if(l==L&&r==R)return t[now];
	int mid=(l+r)>>1;
	if(R<=mid)return Query(lson,l,mid,L,R);
	if(L>mid)return Query(rson,mid+1,r,L,R);
	return min(Query(lson,l,mid,L,mid),Query(rson,mid+1,r,mid+1,R));
}
void Topsort()
{
	for(int i=1;i<=n;++i)if(!dg[i])Q.push(i);
	while(!Q.empty())
	{
		int u=Q.top();Q.pop();ans[u]=++tot;
		for(int i=h[u];i;i=e[i].next)
			if(!--dg[e[i].v])Q.push(e[i].v);
	}
}
int main()
{
	n=read();K=read();
	for(int i=1;i<=n;++i)a[P[i]=read()]=i;
	memset(t,63,sizeof(t));
	for(int i=n;i;--i)
	{
		int x=Query(1,1,n,a[i]+1,min(a[i]+K-1,n));
		if(x<1e9)Add(a[i],a[x]);
		x=Query(1,1,n,max(1,a[i]-K+1),a[i]-1);
		if(x<1e9)Add(a[i],a[x]);
		Modify(1,1,n,a[i],i);
	}
	Topsort();
	for(int i=1;i<=n;++i)printf("%d\n",ans[i]);
	return 0;
}

posted @ 2018-09-11 10:47  小蒟蒻yyb  阅读(431)  评论(0编辑  收藏  举报