10.16模拟赛
sol:
原题 CF444E
引理:考虑把xi像size一样记录出某个子树的大小,如果这个子树的大小大于除这个子树外的节点数,那这条边就可以满足条件。
但是不能用整个树来做判断,可以把他们看成一块块的,首先对每条边按边权排序,然后用并查集把点并起来,顺便把sz合起来,顺便同时判断是否满足
#include <cstdio> #include <algorithm> using namespace std; const int N=100005; int n,fa[N],sz[N],bo=0,pp[N],S=0; inline int Find(int x){return (x==fa[x])?x:fa[x]=Find(fa[x]);} struct node{int x,y,w;}a[N]; inline bool cmp(node a,node b){return a.w<b.w;} inline void merg(int x,int y) { fa[x]=y; sz[y]+=sz[x]; pp[y]+=pp[x]; if(sz[y]>S-pp[y])bo=1; } int main() { int i,x,y,re=0; scanf("%d",&n); for(i=1;i<n;i++) { scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w); fa[i]=i; sz[i]=1; }fa[n]=n; sz[n]=1; sort(a+1,a+n,cmp); for(i=1;i<=n;i++)scanf("%d",&pp[i]),S+=pp[i]; for(i=1;i<n;i++) { if(bo)break; x=Find(a[i].x); y=Find(a[i].y); re=a[i].w; merg(x,y); }printf("%d\n",re); }
sol:树形DP,可知从一个点出发有两部分答案,一部分是在它的子树内的答案,另一部分是在子树外的。统计所有节点两部分的和就是答案了,代码实现挺妙的,
#include <cstdio> using namespace std; const int N=200005,M=400005,B=155; int n,k,tot=0,Next[M],to[M],head[M],f[N][B],g[N][B]; long long s1[N],s2[N],re=0; inline void add(int x,int y){Next[++tot]=head[x];to[tot]=y;head[x]=tot;} inline void dfs(int x,int fa) { int i,j; f[x][0]=1; for(i=head[x];i;i=Next[i]) if(to[i]!=fa) { dfs(to[i],x); for(j=0;j<k-1;j++)f[x][j+1]+=f[to[i]][j]; f[x][0]+=f[to[i]][k-1]; s1[x]+=(long long)s1[to[i]]+f[to[i]][0]; }re+=s1[x]; } inline void dfs1(int x,int fa) { int i,j; if(x!=1) { for(j=0;j<k-1;j++)g[x][j+1]+=g[fa][j]; g[x][0]+=g[fa][k-1]; s2[x]+=(long long)s2[fa]+g[fa][0]; re+=s2[x]; }for(j=0;j<k;j++)g[x][j]+=f[x][j]; s2[x]+=s1[x]; for(i=head[x];i;i=Next[i])if(to[i]!=fa) { for(j=0;j<k-1;j++)g[x][j+1]-=f[to[i]][j]; g[x][0]-=f[to[i]][k-1]; s2[x]-=(long long)s1[to[i]]+f[to[i]][0]; dfs1(to[i],x); for(j=0;j<k-1;j++)g[x][j+1]+=f[to[i]][j]; g[x][0]+=f[to[i]][k-1]; s2[x]+=(long long)s1[to[i]]+f[to[i]][0]; } } int main() { int i,x,y; scanf("%d%d",&n,&k); for(i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); }dfs(1,0); dfs1(1,0); printf("%lld\n",1LL*re/2LL); }
河田は河田、赤木は赤木……。
私は誰ですか。教えてください、私は誰ですか。
そうだ、俺はあきらめない男、三井寿だ!