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
翻译
翻译其实有点问题。
应该是
题解
这题可以说非常妙了。
我们可以把这个值看做在网格图上的一点\((-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;
}