[bzoj3252]攻略_dfs序_线段树_贪心
攻略 bzoj-3252
题目大意:给定一棵n个节点的有根树,点有点权。让你选出至多k个节点,使得他们到根的链的并最大。
注释:$1\le n\le 2\cdot 10^5$,$1\le val_i\le 2^{31}-1$。
想法:这题模拟赛T2,正解可并堆,我用的$dfs$序加线段树。
考虑暴力:显然每次选取当前贡献最大的一定是最优的,一个非常显然的贪心。
我们对每个节点维护一个$dis$表示当前节点选取的贡献。如果我们选取了一个节点$x$那么以$x$为根的子树都会减去$val_x$。
显然一个节点只会被选取一次,所以如果我们每次可以快速找到最大值的位置,我们就可以对以$x$为根的子树进行子树减。
故此我们在线段树上的每个节点都维护一个$son$信息,表示线段树上的最大值是左儿子贡献的还是右儿子贡献的。
对于线段树上的叶子节点再维护一下这个叶子对应原树的哪个节点。
这样的话每次我们从根开始通过那个$son$信息找到最大的贡献节点,然后从当前节点开始向根遍历知道遇到被选取过的节点。每遍历到一个节点都进行一遍子树减即可。
总时间复杂度$O(nlogn)$。
Code:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define N 200010 #define ls p<<1 #define rs p<<1|1 using namespace std; typedef long long ll; struct Node { ll mx,del; int id,son; }a[N<<2]; ll dis[N]; int dic[N],cnt,val[N],re[N],f[N],size[N]; int to[N<<1],nxt[N<<1],head[N],tot; bool vis[N]; inline char nc() {static char *p1,*p2,buf[100000]; return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;} ll rd() {ll x=0; char c=nc(); while(!isdigit(c)) c=nc(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=nc(); return x;} inline void add(int x,int y) {to[++tot]=y; nxt[tot]=head[x]; head[x]=tot;} inline void pushup(int p) { a[p].mx=max(a[ls].mx,a[rs].mx); a[p].son=(a[ls].mx>=a[rs].mx?1:2); } inline void pushdown(int p) { if(!a[p].del) return; a[ls].mx+=a[p].del; a[ls].del+=a[p].del; a[rs].mx+=a[p].del; a[rs].del+=a[p].del; a[p].del=0; } void build(int l,int r,int p) { if(l==r) {a[p].mx=dis[re[l]],a[p].id=re[l]; return;} int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); pushup(p); } void update(int x,int y,ll val,int l,int r,int p) { if(x<=l&&r<=y) { a[p].mx+=val; a[p].del+=val; return; } int mid=(l+r)>>1; pushdown(p); if(x<=mid) update(x,y,val,l,mid,ls); if(mid<y) update(x,y,val,mid+1,r,rs); pushup(p); } int query_son(int l,int r,int p) { if(l==r) return p; int mid=(l+r)>>1; pushdown(p); if(a[p].son==1) return query_son(l,mid,ls); else return query_son(mid+1,r,rs); } void dfs(int pos,int fa) { dis[pos]=dis[fa]+val[pos]; f[pos]=fa; dic[pos]=++cnt; re[cnt]=pos; size[pos]=1; for(int i=head[pos];i;i=nxt[i]) if(to[i]!=fa) { dfs(to[i],pos); size[pos]+=size[to[i]]; } } int main() { ll ans=0; int n=rd(),k=rd(); for(int i=1;i<=n;i++) val[i]=rd(); for(int i=1;i<n;i++) { int x=rd(),y=rd(); add(x,y); add(y,x); } dfs(1,0); build(1,n,1); while(k--) { int p=query_son(1,n,1); ans+=a[p].mx; int x=a[p].id; while(x&&!vis[x]) { update(dic[x],dic[x]+size[x]-1,-val[x],1,n,1); val[x]=0; vis[x]=true; x=f[x]; } } cout << ans << endl ; // fclose(stdin); fclose(stdout); return 0; }
小结:从暴力入手是一个非常好的选择。
| 欢迎来原网站坐坐! >原文链接<