interlinkage:
description:
有一颗$n$个节点的树,每条边有一个边权
对于一个$x$,删去一些边使得每个点的度数都不超过$x$,最小化删去边的边权并输出
需要一次输出$x=0->n-1$的值
$1<=n<=250000$
solution:
- part1
- 先考虑单个$x$的做法。任选一个根,设$f_{u,0/1}$表示以节点$u$为根的子树内,节点$u$与它的父亲不断/断的最小代价;
- 显然这个可以讨论转移,但我们换一个更好的角度;
- 设$v$为$u$的一个儿子,若$f_{v,1}+c<=f_{v,0}$,即断的话比不断更优秀的,那么我们一定选择断,因为这样还可以让$u$的度数小$1$。这种情况我们就直接加上$f_{v,1}+c$,并让$u$度数$--$;
- 否则的话我们就先假设这条边不断,加上$f_{v,0}$。那么到最后可能会发现$u$的度数不满足不超过$x$,显然我们要把若干个$f_{v,0}$变成$f_{v,1}+c$;
- 我们对每个点维护一个$f_{v,1}+c-f_{v,0}$的堆,取度数-$x$个最小的就好了;
- 这里的堆本质上维护的就是通过多大的代价能让度数减一;
- part2
- 现在考虑多个$x$,从小做到大
- 有一个很显然的想法,若是一个点原来的度数就不超过$x$,那么这个点可以直接删掉;
- 做法是把所有以这个点为端点的边放到这些边的另一个端点的堆里面,表示可以通过断掉这条边来使得度数减一;
- 剩下的就是对所有有用的点,即度数大于$x$的点做上面的$DP$就好了;
- part3
- 复杂度显然为$\sum_{x=0}^{n-1}\sum_{i=1}^{n}[du_i>x]=\sum_{i=1}^{n}du_i=O(n)$;
code:
#include#include #include #include #include #include #define fi first#define se second#define pb push_backusing namespace std;typedef long long ll;typedef pair pll;const ll N=250005;ll n;ll du[N],nxt[N],vis[N];ll sum[N];vector e[N];vector d[N];inline ll read(){ char ch=getchar();ll s=0,f=1; while (ch<'0'||ch>'9') { if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f;}bool cmp(pll x,pll y) { return du[x.fi] A,B; void push(ll x) {A.push(x);} void del(ll x) {B.push(x);} ll top() { while (!B.empty()&&A.top()==B.top()) A.pop(),B.pop();return A.top();} void pop() {top();A.pop();} ll size() { return A.size()-B.size();}}h[N];void upd(ll x,ll num){ while (h[x].size()>num) { sum[x]=sum[x]-h[x].top(); h[x].pop(); }}void upd1(ll x,ll num,vector &add){ while (h[x].size()>num) { sum[x]=sum[x]-h[x].top(); add.pb(h[x].top()); h[x].pop(); }}void dele(ll x){ vis[x]=1; for (ll i=0;i add,del; add.clear();del.clear(); ll siz=e[x].size(),tot=0; while (st[x] <=D) st[x]++; for (ll i=st[x];i =1;i--) { if (d[i+1].size()) nxt[i]=i+1; else nxt[i]=nxt[i+1]; } memset(vis,0,sizeof(vis)); for (ll u=1;u