AtCoder Grand Contest 009
AtCoder Grand Contest 009
A - Multiple Array
翻译
题解
从后往前考虑。
#include<iostream>
#include<cstdio>
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 n,a[MAX],b[MAX];
ll ans=0;
int main()
{
n=read();
for(int i=1;i<=n;++i)a[i]=read(),b[i]=read();
for(int i=n;i>=1;--i)
{
a[i]=(b[i]-(a[i]+ans)%b[i])%b[i];
ans+=a[i];
}
cout<<ans<<endl;
return 0;
}
B - Tournament
翻译
有\(n\)个人参加一个锦标赛,因为是淘汰赛制,所以一共会进行\(n-1\)场。现在已知\(1\)号选手是最终的获胜者,并且知道除了\(1\)号之外的每一个人是被谁给淘汰的。求出所有人中任何一个人赢得冠军所需要的最小胜场树数。(就是让你求这棵树的最小可能深度)
题解
显然根据谁被谁给淘汰的信息可以构建一棵树,那么一个人赢得所有人就是它的儿子。然后不就是一个傻逼贪心题了吗。设\(f[i]\)表示子树中任何一个点想要达到\(i\)这个位置最多需要赢得场次的最小值。显然按照所有儿子的\(f\)值排序然后贪心顺次选择即可。不懂看代码。
#include<iostream>
#include<cstdio>
#include<algorithm>
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;
}
struct Line{int v,next;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,f[MAX],S[MAX],top;
void dfs(int u)
{
for(int i=h[u];i;i=e[i].next)dfs(e[i].v);
top=0;
for(int i=h[u];i;i=e[i].next)S[++top]=f[e[i].v];
sort(&S[1],&S[top+1]);
for(int i=1;i<=top;++i)f[u]=max(f[u],S[i]+top-i+1);
}
int main()
{
n=read();
for(int i=2;i<=n;++i)Add(read(),i);
dfs(1);printf("%d\n",f[1]);
return 0;
}
C - Division into Two
翻译
题解
考虑一个\(O(n^2)\)的暴力,设\(f[i][j]\)表示第一个集合中的最后一个数是\(i\),第二个集合中的最后一个数是\(j\)的方案数,把所有数全部排序之后从前往后依次放就好了(事实上给定的数就是有序的)。
先不妨令\(A>B\),这样子如果存在三个元素两两之间的差都小于\(B\)显然就无解,直接判掉。直接判掉这种情况之后,对于较小的\(B\),显然有且仅有相邻的两个元素可能会出现不合法的情况了。
设\(f[i]\)表示第一个集合中最后一个选取的数是\(i\) 的方案数,考虑哪些\(j\)可以转移过来,首先\(j<i\)(废话),然后\(s[i]-s[j]\ge A\),并且\([j+1,i-1]\)这一段两两之差都大于等于\(B\)。显然\(j\)也是连续的一段,前缀和随便维护一下就好了。
太弱了,能够想到大部分就是最后的细节想不清
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MOD 1000000007
#define MAX 100100
inline ll read()
{
ll 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;
}
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int n,f[MAX],s[MAX];
ll A,B,a[MAX];
int main()
{
n=read();A=read();B=read();if(A>B)swap(A,B);
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n-2;++i)
if(a[i+2]-a[i]<A){puts("0");return 0;}
f[0]=s[0]=1;
for(int i=1,p=0,lim=0;i<=n;++i)
{
while(p<i&&a[i]-a[p+1]>=B)++p;
if(lim<=p)add(f[i],(s[p]+MOD-(lim?s[lim-1]:0))%MOD);
add(s[i],s[i-1]+f[i]);
if(i>1&&a[i]-a[i-1]<A)lim=i-1;
}
int ans=0;
for(int i=n;~i;--i)
{
add(ans,f[i]);
if(i<n&&a[i+1]-a[i]<A)break;
}
printf("%d\n",ans);
return 0;
}
D - Uninity
翻译
看半天看不懂系列。最后百度才知道什么意思。。。
类似点分治过程,只不过分治中心任意选择。 求点分树最小深度。
题解
毫无思路系列、不看题解不会做系列。
首先都说了类似于点分治的过程,那么加入我们直接搬点分治,这个答案不会超过\(log\)。然而对于直接做题没有太多的帮助。考虑这样一个性质,假设我们把这个点分树给构出来之后,有两个点的深度相同,都为\(K\),那么不难证明他们在原树中的路径上至少存在一个点满足深度小于\(K\)。现在把深度反过来看,即从叶子节点开始倒着填数,变成了任意两个权值相等的点的路径上都存在一个点的权值大于他们的权值,要求最小化最大权值。那么利用贪心考虑填数,因为答案不会超过\(log\),所以可以直接用一个二进制数把子树中拥有的、没有匹配上的每个权值的点个压一下。
#include<iostream>
#include<cstdio>
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;
}
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 f[MAX][25];
int ans,n;
void dfs(int u,int ff)
{
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(v==ff)continue;
dfs(v,u);
for(int j=0;j<=20;++j)f[u][j]+=f[v][j];//统计儿子中所有未被匹配的每个标号的个数
}
int p=0;
for(int i=20;~i;--i)
if(f[u][i]>1){p=i+1;break;}//如果超过了两个,那么必须在LCA处匹配
while(f[u][p])++p;++f[u][p];//必须要找一个没有出现过的标号,否则儿子中的那个标号的LCA就是当前点
for(int i=0;i<p;++i)f[u][i]=0;//所有小于当前值的都匹配上了
ans=max(ans,p);
}
int main()
{
n=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
dfs(1,0);printf("%d\n",ans);
return 0;
}
E - Eternal Average
翻译
黑板上有\(n\)个\(0\)和\(m\)个\(1\)。每次可以选择黑板上所有的数,然后选择\(K\)个,把他们擦掉,再把他们的平均数给写上去,问最终剩下的那一个数有多少种取值。保证\(n+m-1\)被\(K-1\)整除。
题解
我们把整个过程可以用一棵树来表示。显然这是一棵\(K\)叉树,有\(n+m\)个叶子,每个点的权值为所有儿子的权值的平均值。这样以来,我们可以分开考虑每一个叶子节点对于答案的贡献,显然,这个值是\(\sum k^{-dep}\),即所有权值为\(1\)的叶子节点的\(K\)的深度次方分之一的和。
那么,问题被转化成了,求有多少个\(z(0\lt z\lt 1)\)可以被拆分成恰好\(n\)个\(\frac{1}{k}\)的若干次幂。把\(01\)反过来考虑,同时\(1-z\)要能够被恰好拆分成\(m\)个\(\frac{1}{k}\)的若干次幂。反过来成立也很好证明,你先假装所有点的点权都是\(1\),那么根节点的值显然也是\(1\),那么\(\sum_{i=1}^nk^{-dep}+\sum_{i=1}^mk^{-dep}=1\),减一下显然成立。
考虑如何求解\(z\),首先\(z\)能够被分解成\(n\)个\(\frac{1}{k}\)的若干次幂。我们把\(z\)写成\(k\)进制的形式,假设其为\(z=0.c_1c_2...c_l\)。考虑\(\sum c_i\)和\(n\)之间的关系。如果没有进位的情况出现,那么显然\(\sum c=n\),如果出现了进位的情况,显然是低位减去了一个\(k\),然后高位加上了一个\(1\)。那么只需要\(\sum c \equiv n(mod\ k-1)\)就好了。\(1-z\)和\(m\)之间的关系同理。值得注意的一点是,在这个状态中\(c_l\)不能为零。
那么我们可以来搞\(dp\)了,设\(f[i][j]\)表示考虑了前\(i\)位(小数点后的位),他们的和是\(j\)的方案数。因为钦定最后一位不是\(0\),所以额外记录一下最后一个填的是不是零。
#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 1000000007
#define MAX 2020
int f[MAX<<1][MAX][2],s[MAX],n,m,K,ans;
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
int main()
{
cin>>n>>m>>K;m-=1;K-=1;
f[0][0][0]=1;
for(int i=1;i<=max(n,m)<<1;++i)
{
for(int j=0;j<=n;++j)s[j+1]=((s[j]+f[i-1][j][0])%MOD+f[i-1][j][1])%MOD;
for(int j=0;j<=n;++j)
{
f[i][j][0]=(s[j+1]-s[j]+MOD)%MOD;
int k=max(0,j-K);
f[i][j][1]=(s[j]-s[k]+MOD)%MOD;
}
for(int j=0;j<=n;++j)
if(j%K==n%K&&(i*K-j)%K==m%K&&i*K-j<=m)
add(ans,f[i][j][1]);
}
cout<<ans<<endl;
return 0;
}