啊,好久没写博客了,主要原因是学校的内部训练……不过做过的题目还是总结一下比较好。这里先写2017国庆广州班我能写出来的题吧……
Day1T1 平衡 balance
题目大意:
做法:原题啊,水的不行,直接暴力水过……
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,k=0,a[50010],c[50010],ans=0;
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
k+=a[i];
}
k/=n;
c[0]=0;
for(int i=1;i<n;i++)
{
c[i]=k-a[i]+c[i-1];
ans+=abs(c[i]);
}
printf("%lld",ans);
return 0;
}
Day1T2 道路 road
题目大意:一个
做法:本题需要用到期望+BFS/DFS+DAG最长路。
考虑每一个高度相同的连通块。可以证明,如果该连通块包含
其中边界条件为
我们可以用数学归纳法证明
首先对于
对于任意
两边同除
那么我们用BFS或DFS把高度相同的连通块缩起来,再从高度高的块向低的连边,就构成一个DAG,按拓扑序跑个DP求最长路即可。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,tot=0,h[1010][1010],belong[1010][1010],in[1000010]={0},siz[1000010]={0};
int tt=0,first[1000010]={0},q[1000010],head=1,tail=0;
ll v[1010][1010],sum[1000010]={0},f[1000010]={0},ans=0;
bool vis[1010][1010]={0};
struct edge {int v,next;} e[2000010];
void dfs(int x,int y,int hi)
{
vis[x][y]=1;
belong[x][y]=tot;
siz[tot]++,sum[tot]+=v[x][y];
if (x>1&&h[x-1][y]==hi&&!vis[x-1][y]) dfs(x-1,y,hi);
if (y>1&&h[x][y-1]==hi&&!vis[x][y-1]) dfs(x,y-1,hi);
if (x<n&&h[x+1][y]==hi&&!vis[x+1][y]) dfs(x+1,y,hi);
if (y<m&&h[x][y+1]==hi&&!vis[x][y+1]) dfs(x,y+1,hi);
}
void insert(int x,int y)
{
e[++tt].v=y;
e[tt].next=first[x];
first[x]=tt;
in[y]++;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&h[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&v[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if (!vis[i][j]) ++tot,dfs(i,j,h[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if (i>1&&h[i][j]>h[i-1][j]) insert(belong[i][j],belong[i-1][j]);
if (j>1&&h[i][j]>h[i][j-1]) insert(belong[i][j],belong[i][j-1]);
if (i<n&&h[i][j]>h[i+1][j]) insert(belong[i][j],belong[i+1][j]);
if (j<m&&h[i][j]>h[i][j+1]) insert(belong[i][j],belong[i][j+1]);
}
for(int i=1;i<=tot;i++)
{
if (siz[i]>1) sum[i]*=2;
f[i]=0;
if (!in[i]) q[++tail]=i;
}
while(head<=tail)
{
int v=q[head];
f[v]+=sum[v];
ans=max(ans,f[v]);
for(int i=first[v];i;i=e[i].next)
{
in[e[i].v]--;
f[e[i].v]=max(f[e[i].v],f[v]);
if (!in[e[i].v]) q[++tail]=e[i].v;
}
head++;
}
printf("%lld",ans);
return 0;
}
Day1T3 小豪的花园 flower
题目大意:维护一棵树,点有非负点权,维护以下操作:单点修改权值,对一棵子树中所有点取模(模数可能不同),询问路径上的点权和。
做法:本题需要用到树链剖分。
虽然我们一眼就能看出这是一道树链剖分的题目,但是没做过区间取模的同学可能会对这个操作比较迷惑。事实上,我们可以证明每个数在不被修改的情况下,最多被有效取模的次数是
当
当
这样每次有效取模都至少少了一半,那么有效取模次数当然就是
所以我们对于线段树内每个区间维护一个最大值,如果取模时这个区间最大值小于模数,那么这个区间就不用管,否则暴力向下取模即可。
然而还有单点修改,其实并不用担心,每个数被取模的次数仍然不超过
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,tot=0,first[100010]={0},in[100010],out[100010],q[100010];
int dep[100010],fa[100010],siz[100010],son[100010]={0},top[100010];
ll a[100010]={0},sum[400010],mx[400010];
struct edge {int v,next;} e[200010];
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void dfs1(int v)
{
siz[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa[v])
{
fa[e[i].v]=v;
dep[e[i].v]=dep[v]+1;
dfs1(e[i].v);
siz[v]+=siz[e[i].v];
if (siz[e[i].v]>siz[son[v]]) son[v]=e[i].v;
}
}
void dfs2(int v,int tp)
{
in[v]=++tot;q[tot]=v;top[v]=tp;
if (son[v]) dfs2(son[v],tp);
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa[v]&&e[i].v!=son[v]) dfs2(e[i].v,e[i].v);
out[v]=tot;
}
void pushup(int no)
{
mx[no]=max(mx[no<<1],mx[no<<1|1]);
sum[no]=sum[no<<1]+sum[no<<1|1];
}
void buildtree(int no,int l,int r)
{
if (l==r)
{
sum[no]=mx[no]=a[q[l]];
return;
}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
void modify(int no,int l,int r,int x,ll y)
{
if (l==r)
{
sum[no]=mx[no]=y;
return;
}
int mid=(l+r)>>1;
if (x<=mid) modify(no<<1,l,mid,x,y);
else modify(no<<1|1,mid+1,r,x,y);
pushup(no);
}
void Mod(int no,int l,int r,int s,int t,ll k)
{
if (l==r)
{
sum[no]%=k,mx[no]%=k;
return;
}
int mid=(l+r)>>1;
if (s<=mid&&mx[no<<1]>=k) Mod(no<<1,l,mid,s,t,k);
if (t>mid&&mx[no<<1|1]>=k) Mod(no<<1|1,mid+1,r,s,t,k);
pushup(no);
}
ll Sum(int no,int l,int r,int s,int t)
{
if (l>=s&&r<=t) return sum[no];
int mid=(l+r)>>1;
ll S=0;
if (s<=mid) S+=Sum(no<<1,l,mid,s,t);
if (t>mid) S+=Sum(no<<1|1,mid+1,r,s,t);
return S;
}
ll query(int x,int y)
{
ll ans=0;
while(top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
ans+=Sum(1,1,n,in[top[x]],in[x]);
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
ans+=Sum(1,1,n,in[y],in[x]);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
dep[1]=fa[1]=siz[0]=0;
dfs1(1);
tot=0;dfs2(1,1);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
buildtree(1,1,n);
for(int i=1;i<=m;i++)
{
int op;
scanf("%d",&op);
if (op==1)
{
int a;ll b;
scanf("%d%lld",&a,&b);
Mod(1,1,n,in[a],out[a],b);
}
if (op==2)
{
int a;ll b;
scanf("%d%lld",&a,&b);
modify(1,1,n,in[a],b);
}
if (op==3)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%lld
",query(a,b));
}
}
return 0;
}
Day2T1 数学课 number
题目大意:给出
做法:本题需要用到贪心。
贪心策略为每次选择最大的两个数字擦除,可以证明这样得到的最后的数字是最小的。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000000007
using namespace std;
int n;
ll a[210];
bool cmp(ll a,ll b)
{
return a<b;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
sort(a+1,a+n+1,cmp);
for(int i=n;i>=2;i--) a[i-1]=(a[i]*a[i-1]+1)%mod;
printf("%lld",a[1]);
return 0;
}
Day2T2 遗传病 disease
题目大意:维护一个集合,每次操作往集合中加入或从集合中删除一个数,每次操作后询问集合中互质的数对个数。数字不大于
做法:本题需要用到莫比乌斯反演定理+线性筛求积性函数。
设
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
int n,m,prime[500010],a[500010],t=0;
ll miu[500010],s[500010],ans=0;
bool non_prime[500010]={0},chosen[100010]={0};
void calc_miu()
{
for(int i=1;i<=500000;i++) miu[i]=1;
for(int i=2;i<=500000;i++)
{
if (!non_prime[i])
{
prime[++t]=i;
miu[i]=-1;
}
for(int j=1;i*prime[j]<=500000&&j<=t;j++)
{
non_prime[i*prime[j]]=1;
if (i%prime[j]==0)
{
miu[i*prime[j]]=0;
break;
}
miu[i*prime[j]]=-miu[i];
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
calc_miu();
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
if (!chosen[x])
{
chosen[x]=1;
for(int i=1;i<=sqrt(a[x]);i++)
if (a[x]%i==0)
{
s[i]++;
ans+=miu[i]*(s[i]-1);
if (a[x]/i!=i)
{
s[a[x]/i]++;
ans+=miu[a[x]/i]*(s[a[x]/i]-1);
}
}
}
else
{
chosen[x]=0;
for(int i=1;i<=sqrt(a[x]);i++)
if (a[x]%i==0)
{
s[i]--;
ans-=miu[i]*s[i];
if (a[x]/i!=i)
{
s[a[x]/i]--;
ans-=miu[a[x]/i]*s[a[x]/i];
}
}
}
printf("%lld
",ans);
}
return 0;
}
Day2T3 瓜分土地 B
题目大意:将一个数列分成三个非空连续段,令每段内数字和分别为
做法:本题需要用到线段树+离散化+二分+分类讨论。
归根到底,一种划分方案只有两个实质上的变量:第一段的右端点和第二段的左端点。我们考虑枚举第一段的右端点,那么我们就需要求出这种情况下划分方案合理值的最小值。
观察目标函数,我们可以对
不过还有一点要注意,第三段左端点必须在第一段左端点右边那个元素的右边,所以我们每次计算完第一段右端点为某点的情况,要把第三段右端点为该点右边相邻节点的情况去除,即把存储的式子值都改成
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,a[100010],x,rht[100010],sum,inf,ans,lft,mn[400010][6],now;
bool flag;
struct forsort
{
ll val,id;
} f[100010];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
void pushup(int no)
{
for(int i=0;i<6;i++)
mn[no][i]=min(mn[no<<1][i],mn[no<<1|1][i]);
}
void buildtree(int no,ll l,ll r)
{
if (l==r)
{
mn[no][0]=-f[l].val-x*f[l].id;
mn[no][1]=f[l].val-x*f[l].id;
mn[no][2]=-2*f[l].val-x*f[l].id;
mn[no][3]=-f[l].val-x*f[l].id;
mn[no][4]=2*f[l].val-x*f[l].id;
mn[no][5]=f[l].val-x*f[l].id;
return;
}
ll mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
ll query(int no,ll l,ll r,ll s,ll t,int mode)
{
if (l>=s&&r<=t) return mn[no][mode];
ll mid=(l+r)>>1,minn=inf;
if (s<=mid) minn=min(minn,query(no<<1,l,mid,s,t,mode));
if (t>mid) minn=min(minn,query(no<<1|1,mid+1,r,s,t,mode));
return minn;
}
void solve(ll s,ll t,int mode)
{
ll l=0,r=n+1,sl,sr;
flag=1;
while(l<r)
{
ll mid=(l+r)>>1;
if (f[mid].val<s) l=mid+1;
else r=mid;
}
sl=l;l=0,r=n+1;
while(l<r)
{
ll mid=(l+r)>>1;
if (f[mid].val<=t) l=mid+1;
else r=mid;
}
sr=l-1;
if (sl>sr) {flag=0;return;}
now=query(1,1,n,sl,sr,mode);
}
ll fl(ll x)
{
if (x>=0) return x/2;
else return (x/2)-((abs(x)%2==0)?0:1);
}
int main()
{
inf=1000000000;inf*=inf;
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
scanf("%lld",&a[i]);
scanf("%lld",&x);
rht[0]=0;
for(ll i=1;i<=n;i++)
rht[i]=rht[i-1]+a[n-i+1];
sum=rht[n];
for(ll i=1;i<=n;i++) f[i].val=rht[i],f[i].id=i;
sort(f+1,f+n+1,cmp);
f[0].val=-inf-1;f[n+1].val=inf+1;
buildtree(1,1,n);
lft=0;
ans=inf;
for(ll i=1;i<=n;i++)
{
lft+=a[i];
solve(sum-2*lft,fl(sum-lft),0);
if (flag) ans=min(ans,x*(n-i)+lft+now);
solve(fl(sum-lft+1),lft,1);
if (flag) ans=min(ans,x*(n-i)+2*lft-sum+now);
solve(-inf,min(sum-2*lft,lft),2);
if (flag) ans=min(ans,x*(n-i)+sum-lft+now);
solve(lft,fl(sum-lft),3);
if (flag) ans=min(ans,x*(n-i)+sum-2*lft+now);
solve(max(lft,sum-2*lft),inf,4);
if (flag) ans=min(ans,x*(n-i)+lft-sum+now);
solve(fl(sum-lft+1),sum-2*lft,5);
if (flag) ans=min(ans,x*(n-i)-lft+now);
}
printf("%lld
",ans);
return 0;
}
Day3T1 树 bst
题目大意:对一个
做法:本题需要用到链表+二分(但我脑抽写了个线段树)。
观察之后我们发现,一开始,插入每个点后它的深度都会是
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n;
bool vis[1200010];
ll ans=0,s[300010]={0};
void pushup(int no)
{
vis[no]=vis[no<<1]||vis[no<<1|1];
}
void buildtree(int no,int l,int r)
{
if (l==r)
{
if (l==0||l==n+1) vis[no]=1;
else vis[no]=0;
return;
}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
int findleft(int no,int l,int r,int x)
{
if (r<x) return 0;
if (l==r&&l>=x&&vis[no]) return l;
if (l>=x&&!vis[no]) return 0;
int mid=(l+r)>>1,find=0;
if (x<=mid) find=findleft(no<<1,l,mid,x);
if (!find) find=findleft(no<<1|1,mid+1,r,x);
return find;
}
int findright(int no,int l,int r,int x)
{
if (l>x) return 0;
if (l==r&&r<=x&&vis[no]) return l;
if (r<=x&&!vis[no]) return 0;
int mid=(l+r)>>1,find=0;
if (x>mid) find=findright(no<<1|1,mid+1,r,x);
if (!find) find=findright(no<<1,l,mid,x);
return find;
}
void modify(int no,int l,int r,int x)
{
if (l==r) {vis[no]=1;return;}
int mid=(l+r)>>1;
if (x<=mid) modify(no<<1,l,mid,x);
else modify(no<<1|1,mid+1,r,x);
pushup(no);
}
int lowbit(int i)
{
return i&(-i);
}
ll sum(int i)
{
ll ss=0;
for(;i>0;i-=lowbit(i))
ss+=s[i];
return ss;
}
void add(int i,ll x)
{
for(;i<=n+1;i+=lowbit(i))
s[i]+=x;
}
int main()
{
scanf("%d",&n);
buildtree(1,0,n+1);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
ans+=sum(x);
int l,r;
l=findright(1,0,n+1,x);
r=findleft(1,0,n+1,x);
modify(1,0,n+1,x);
if (l<=r)
{
if (l>0) add(l,1);
else add(1,1);
add(r,-1);
}
printf("%lld
",ans);
}
return 0;
}
Day3T2 赛 ctsc
题目大意:有
做法:本题需要用到组合数学+DP。
这个题面看上去非常之复杂,复杂到无以复加,但是理性分析和观察(看完题解)后,我们发现,每个选手都有一个得分最大值和得分最小值,最大值就是该选手没评测的题目都过了的分数,最小值就是没评测的题目都没过的分数。按照选手的得分最大值从大到小排序,如果最大值相同按序号从小到大排序。令
求出
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,m,score[55],p[55],s,t;
char ss[55];
struct oier
{
ll minn,maxx,id;
} f[55];
bool cmp(oier a,oier b)
{
if (a.maxx!=b.maxx) return a.maxx>b.maxx;
else return a.id<b.id;
}
ll calc_c(ll n,ll m)
{
if (m>n) return 0;
if (m==0) return 1;
if (n==0) return 0;
if (m==n) return 1;
ll ans=1;
for(ll i=0;i<n-m;i++)
ans=ans*(n-i)/(1+i);
return ans;
}
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
scanf("%lld",&score[i]);
scanf("%lld",&m);
for(ll i=1;i<=m;i++)
{
scanf("%s",ss);
f[i].maxx=f[i].minn=0;
for(ll j=1;j<=n;j++)
{
if (score[j]>0&&ss[j-1]=='Y')
{
f[i].maxx+=score[j];
f[i].minn+=score[j];
}
if (score[j]<0&&ss[j-1]=='Y')
f[i].maxx-=score[j];
}
f[i].id=i;
}
scanf("%lld%lld",&s,&t);
sort(f+1,f+m+1,cmp);
for(ll i=1;i<=m;i++)
{
p[i]=0;
for(int j=1;j<i;j++)
p[i]+=((f[j].minn>f[i].maxx)||((f[j].minn==f[i].maxx)&&(f[j].id<f[i].id)));
}
ll ans=0;
for(ll i=t;i<=m;i++)
{
if (p[i]>s-1) break;
ll k_min,k_max;
k_min=max(p[i]-s+t,(ll)0);
k_max=min(p[i],t-1);
for(ll k=k_min;k<=k_max;k++)
ans+=calc_c(p[i],k)*calc_c(i-p[i]-1,t-1-k);
}
printf("%lld",ans);
return 0;
}
Day3T3 史 history
题目大意:有
做法:本题需要用到带权并查集(可持久化并查集的弱化?不会可持久化并查集)。
首先我们知道,两点间是否连通取决于中间连的晚的边的最小时间,于是我们想到维护一个最小生成树和路径上的最大值。看到在线维护最小生成树,有些同学立马想到LCT之类的鬼畜做法,实际上这题没有这么难……
首先,注意到道路只有修建没有拆除,并且时间是不断递增的,因此已经连通的两点间答案是不受新建道路影响的,而道路只有修建没有拆除,这就让我们产生一种猜想:能不能用并查集的变形来解决这一问题?答案是肯定的。
标程中使用的并查集变形是这样做的:令
满足这些性质有什么用呢?接下来“查”操作说明了一切。我们这里不单单是要查某点所属集合的根节点,而是要查某点在某时刻之前所属集合的根节点。这时候上面的性质2就保证了我们在向上搜的时候,搜到父边大于这个时刻时停止,最后的这个点就是该点在该时刻的父亲,而性质1保证这个过程不会太慢。
这样一来,我们新建道路就是在合并两个集合,而询问就是询问某两个点在某一时刻祖先是否一致,这些我们都可以用上面的并查集变形来解决,于是这个问题就完美解决了,时间复杂度应该是渐近的
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,f[300010],g[300010],h[300010];
char op[3];
int find(int x,int t)
{
while(f[x]>=0&&g[x]<=t) x=f[x];
return x;
}
void merge(int x,int y,int t)
{
x=find(x,t);
y=find(y,t);
if (x!=y)
{
if (h[x]>h[y]) swap(x,y);
f[x]=y,g[x]=t;
h[y]+=(h[x]==h[y]);
}
}
int main()
{
freopen("history.in","r",stdin);
freopen("history.out","w",stdout);
scanf("%d%d",&n,&m);
int c=0,ans=0,x,y,z,t=0;
for(int i=0;i<n;i++) f[i]=-1,g[i]=0,h[i]=1;
for(int i=1;i<=m;i++)
{
scanf("%s",op);
if (op[0]=='K')
{
scanf("%d",&z);
c=(c*ans+z)%n;ans=0;
}
else
{
scanf("%d%d",&x,&y);
x=(c*ans+x)%n,y=(c*ans+y)%n;
if (op[0]=='R') merge(x,y,++t);
else
{
scanf("%d",&z);
z=(c*ans+z)%n;
ans=(find(x,t)==find(y,t))==(find(x,t-z)==find(y,t-z));
printf("%c
",ans?'N':'Y');
}
}
}
return 0;
}
Day4T3 搞 gao
题目大意:
做法:本题需要用到贪心+左偏树。
这题跟APIO那道派遣(dispatching)很像啊……好像就是原题来着……
我们从叶子节点开始考虑以每个点为领导者的最大收益。领导者确定之后,那选出的人肯定越多越好,那么我们对于每个点按费用存储一个大根堆,一开始每个点的堆内都只有该点。然后考虑某个节点的方案时,将该节点的堆与它所有儿子的堆合并起来,如果总费用超过
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,tot=0,first[100010]={0},ch[100010][2];
ll c,f[100010],a[100010],b[100010],siz[100010],rt[100010],dis[100010];
ll sum[100010],val[100010],ans=0;
struct edge {int v,next;} e[100010];
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
if (a[x]<a[y]) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
if (dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
int l=ch[x][0],r=ch[x][1];
dis[x]=dis[r]+1;
sum[x]=sum[l]+sum[r]+a[x];
siz[x]=siz[l]+siz[r]+1;
return x;
}
void delete_root(int v)
{
rt[v]=merge(ch[rt[v]][0],ch[rt[v]][1]);
}
void solve(int v)
{
siz[v]=1,rt[v]=v,ch[v][0]=ch[v][1]=0,dis[v]=-1,sum[v]=a[v];
for(int i=first[v];i;i=e[i].next)
{
solve(e[i].v);
rt[v]=merge(rt[v],rt[e[i].v]);
}
while(sum[rt[v]]>c)
{
delete_root(v);
}
ans=max(ans,siz[rt[v]]*b[v]);
}
int main()
{
scanf("%d%lld",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d%lld%lld",&f[i],&a[i],&b[i]);
if (i>1) insert(f[i],i);
}
solve(1);
printf("%lld",ans);
return 0;
}
啊,所有6天18道题,我也暂时就只会做这10题了T_T,要是之后有新的会做的题可能会补充吧,不过全做完的话,在我的OI生涯中怕是不存在了,真的是弱啊……