A - Multiple Array
从后往前考虑当前数至少要按几次按钮。
注意 的情况。
#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;
}
typedef long long LL;
const int N=100005;
int n;LL ans,a[N],b[N];
int main()
{
n=read();
for(RI i=1;i<=n;++i) a[i]=read(),b[i]=read();
for(RI i=n;i>=1;--i)
if(a[i]+ans) ans=((a[i]+ans-1)/b[i]+1)*b[i]-a[i];
printf("%lld\n",ans);
return 0;
}
B - Tournament
将 与 相连,则构成一棵树结构。每个人必须直接打败的人就是他的儿子(啊咧?),而且要一场一场地打败。
设 表示以 为根的子树中的每个人,想要取得这棵子树内比赛的胜利,需要打多少场比赛。则对于当前 的每个儿子, 要一场一场地打他们,倒数第 个打的人,想要继续胜利就还要打 场比赛。所以我们把 的儿子按照 排序,贪心地决定 打他们的顺序即可。
#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=100005;
int n,tot,h[N],ne[N],to[N],st[N],f[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
bool cmp(int x,int y) {return x>y;}
void dfs(int x) {
int top=0;
for(RI i=h[x];i;i=ne[i]) dfs(to[i]);
for(RI i=h[x];i;i=ne[i]) st[++top]=f[to[i]];
sort(st+1,st+1+top,cmp);
for(RI i=1;i<=top;++i) f[x]=max(f[x],st[i]+i);
}
int main()
{
int x;n=read();
for(RI i=2;i<=n;++i) x=read(),add(x,i);
dfs(1),printf("%d\n",f[1]);
return 0;
}
C - Division into Two
容易想到一种DP,就是 表示 被划分到哪一个集合,并且 被划分到另一个集合的方案数。转移就是 ,对 这一段之间的数两两差值大于等于 , 大于等于 。
那么这个寻找合法 的过程我们可以优化一下,首先 这个条件,我们可以在所有 中二分一下,找到合法 的最大值。而另一个条件,我们可以用RMQ来处理 的差分数组在每个区间内的最小值,然后就也可以二分合法 的最小值。前缀和优化转移即可做到 。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL read() {
LL q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10LL+(LL)(ch-'0'),ch=getchar();
return q;
}
const int mod=1e9+7,N=100005;
const LL inf=2e18;
int n,Log[N],bin[17],f[N][2],sum[N][2];
LL X[2],a[N],mi[17][N];
int qm(int x) {return x>=mod?x-mod:x;}
void prework() {
bin[0]=1;for(RI i=1;i<=16;++i) bin[i]=bin[i-1]<<1;
Log[0]=-1;for(RI i=1;i<=n;++i) Log[i]=Log[i>>1]+1;
for(RI j=1;j<=16;++j)
for(RI i=1;i+bin[j]-1<=n;++i)
mi[j][i]=min(mi[j-1][i],mi[j-1][i+bin[j-1]]);
}
LL getmi(int l,int r) {
if(l>r) return inf;
int t=Log[r-l+1];
return min(mi[t][l],mi[t][r-bin[t]+1]);
}
int findl(int x,int o) {
int l=0,r=x-1,mid,re=0;
while(l<=r) {
mid=(l+r)>>1;
if(getmi(mid+2,x)>=X[o]) re=mid,r=mid-1;
else l=mid+1;
}
return re;
}
int main()
{
n=read(),X[0]=read(),X[1]=read();
for(RI i=1;i<=n;++i) a[i]=read(),mi[0][i]=a[i]-a[i-1];
prework();
f[0][0]=f[0][1]=sum[0][0]=sum[0][1]=1;
a[n+1]=inf;
for(RI i=1;i<=n;++i) {
int l=findl(i,0);
int r=upper_bound(a+1,a+i,a[i+1]-X[1])-a-1;
if(l<=r) f[i][0]=qm(sum[r][1]-(l?sum[l-1][1]:0)+mod);
l=findl(i,1);
r=upper_bound(a+1,a+i,a[i+1]-X[0])-a-1;
if(l<=r) f[i][1]=qm(sum[r][0]-(l?sum[l-1][0]:0)+mod);
sum[i][0]=qm(sum[i-1][0]+f[i][0]);
sum[i][1]=qm(sum[i-1][1]+f[i][1]);
}
printf("%d\n",qm(f[n][0]+f[n][1]));
return 0;
}
D - Uninity
我们逆着思维,来找最后一个将若干Uninity k-1的树连接起来的节点,然后将其删掉,在剩下那些Uninity k-1的树中重复这个操作,这就有点像一个任意选择重心点分治(题外话:随机选取重心的点分治就是XZY-点分治了)。假如我们建出分治树,题目要求的就是分治树可能的最小深度。
首先如果我们就按照点分治的方法来建立分治树,树高就是log级别的,所以这就是上界。
现在我们将分治树连根拔起,倒过来,也就是分治树上的叶子的深度为 ,根的深度为 。然后我们把每个节点在分治树上的深度填回原树上,记作 。则两个 值相同的节点之间,一定有一个 值大于它们的节点。逆过来考虑,只要满足这一点,通过每次将连通块内 值最大节点 选出来当重心,如果发现某个剩下连通块的最大 不是等于 ,则将该连通块 值不断全部加 直到满足为止,一定可以建出合法点分树。
所以我们可以来dfs一次原树,记录每棵子树中对于某个 值,到根的路径上还没有出现大于它的 值的有多少个,然后贪心地决定当前点的 值即可。
#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=100005;
int n,tot,ans;
int h[N],ne[N<<1],to[N<<1],f[N][22];
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) continue;
dfs(to[i],x);
for(RI j=0;j<=20;++j) f[x][j]+=f[to[i]][j];
}
int p=0;
for(RI i=20;i>=0;--i) if(f[x][i]>=2) {p=i+1;break;}
//如果有两棵子树中有这个v值,则当前点的v值必须大于它
while(f[x][p]) ++p;//如果f[x][p]=1,则x到那个v值为p的点之间就没有v值大于它们的点
++f[x][p];for(RI i=0;i<p;++i) f[x][i]=0;
ans=max(ans,p);
}
int main()
{
int x,y;
n=read();
for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
dfs(1,0),printf("%d\n",ans);
return 0;
}
E - Eternal Average
我们把这个问题写成 叉树结构,一共有 片叶子,其中 片写着 , 片写着 。对于一个非叶子节点,它的值是它儿子们的值的平均数。则根节点的数就是黑板上剩下的那个数。
假设那 个 的深度分别是 , 个 的深度分别是 ,则根节点的数就是 。并且我们知道如果所有叶节点的数都是 则根节点也是 ,所以 ,而若满足这个条件,一定也能构造出合法的 叉树(考虑 进制小数的进位,则同一深度的叶子节点一定要有 个才能进以位,依此构造即可)
现在的问题转化为,有多少个 满足 可以写成 个 相加的形式,而 又可以写成 个 相加的形式。(litble的代码里求解的是有多少个满足条件的 ,这当然也没问题)
将 写成 进制小数 ,不考虑进位,则 。考虑还原进位,则可以将 减去 而将 增加 ,则 。假设小数有 位,则 的位数和应该是 。
设 表示小数点后 位,每一位的和是 的方案数进行DP即可。因为小数的末尾不能是0,所以当第 位的末尾是否是0也应该加进状态里面。
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=1e9+7,N=2005;
int n,m,K,ans,f[N<<1][N][2],s[N];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
scanf("%d%d%d",&n,&m,&K);
f[0][0][0]=1;
for(RI i=1;i<=max(n,m)*2;++i) {
s[0]=qm(f[i-1][0][0]+f[i-1][0][1]);
for(RI j=1;j<=n;++j)
s[j]=qm(s[j-1]+qm(f[i-1][j][0]+f[i-1][j][1]));
for(RI j=0;j<=n;++j) {
f[i][j][0]=qm(s[j]-s[j-1]+mod);
if(j) f[i][j][1]=qm(s[j-1]-(j-K>=0?s[j-K]:0)+mod);
}
for(RI j=0;j<=n;++j)
if(j%(K-1)==n%(K-1)&&(i*(K-1)-j+1)%(K-1)==m%(K-1)&&i*(K-1)-j+1<=m)
ans=qm(ans+f[i][j][1]);
}
printf("%d\n",ans);
return 0;
}