Codeforces Global Round 2 1119 F. Niyaz and Small Degrees

题意

现在给你一颗树,边有边权
回答 n n n个询问,分别是对于 x = 0 , 1 , 2.. ( n − 1 ) x=0,1,2..(n-1) x=0,1,2..(n1)
使得每个点的度数都不超过 x x x,最小化删掉的权值

题解

终于补完这题了,来写一下题解

我们先来考虑,对于单个 x x x怎么做
显然可以DP
f i , 0 / 1 f_{i,0/1} fi,0/1表示以 i i i这个节点为根的子树里面, i i i和他父亲的边不断/断的最优代价
至于转移的话,显然可以讨论转移,但是这里考虑一个看起来更可以维护的东西
假设,我们有 f j , 1 + w ≤ f j , 0 f_{j,1}+w \le f_{j,0} fj,1+wfj,0
也就是断了这个肯定更优,那么对于 i i i这个节点一定也是断掉这条边的
那么就先加上这个的贡献,然后 i i i的度数–
否则的话,我们先假设这条边没有断,也就是加上 f j , 0 f_{j,0} fj,0的代价
最后, i i i的度数可能不符合条件,设还需要断掉 n u m num num条边
那么显然是将若干个 f j , 0 f_{j,0} fj,0变为 f j , 1 f_{j,1} fj,1
于是对于每一个节点维护一个 f j , 1 + c − f j , 0 f_{j,1}+c-f_{j,0} fj,1+cfj,0的堆,取 n u m num num个小的就可以了
这里的堆,就是维护一下可以通过增加什么代价使得度数减 1 1 1

考虑多个询问怎么做
x x x从小到大做
可以发现,如果一个点的度数如果不比 x x x大,那么他其实没什么决策的价值
因为选和不选不取决于他了,于是把他删掉,并把以他为端点的边直接丢到另外一个端点的堆里面
表示可以通过断这条边,来使得度数减1
然后剩下的,就只对需要决策的点做上面的DP
一个一个连通块做,然后就没什么了

堆的实现有点技巧,具体来说,就是维护一个sum,表示堆里面的和
然后我们强行让堆里面只有num个元素即可
当然,对于DP时候对堆的修改,要还原
但是无用点加入的边,不需要还原
这里可能有点歧义,具体看代码把

当然,可以使用splay或treap来维护,那么就只需要一个前 k k k大的和,在平衡树上二分即可

至于复杂度,显然就是 ∑ i = 0 n − 1 ∑ j = 1 n [ i ≤ d j ] \sum_{i=0}^{n-1}\sum_{j=1}^n[i\le d_j] i=0n1j=1n[idj]
其中 d d d是度数
显然,这个式子就是所有节点的度数和,也就是 2 n 2n 2n级别
那么总的复杂度就是 n l o g n nlogn nlogn的了

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue> 
using namespace std;
typedef long long LL;
typedef pair<LL,LL> PI;
const LL N=250005;
LL n;
vector<PI> e[N];
vector<LL> vec[N];
LL du[N];
bool cmp (PI x,PI y)	{return du[x.first]<du[y.first];}
LL nxt[N];
struct qq
{
	priority_queue<LL> 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];
bool vis[N];
LL sum[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<LL> & add)
{
	while (h[x].size()>num)	
	{
		sum[x]=sum[x]-h[x].top();add.push_back(h[x].top());h[x].pop();
	}
}
void dele (LL x)
{
	vis[x]=true;
	for (LL u=0;u<e[x].size();u++)
	{
		LL y=e[x][u].first,c=e[x][u].second;	
		if (vis[y]) continue;
		h[y].push(c);	sum[y]=sum[y]+c;
	}
}
LL f[N][2],D;
LL st[N];
void dfs (LL x)
{
	vis[x]=true;LL num=du[x]-D;	
	upd(x,num);
	vector<LL> add,del;
	add.clear();del.clear();
	LL siz=e[x].size(),tot=0;
	while (st[x]<siz&&du[e[x][st[x]].first]<=D) st[x]++;
	for (LL u=st[x];u<siz;u++)
	{
		LL y=e[x][u].first,c=e[x][u].second;
		if (vis[y]) continue;
		dfs(y);
		if (f[y][1]+c<=f[y][0])	{num--;tot=tot+f[y][1]+c;}
		else
		{
			tot=tot+f[y][0];
			LL o=f[y][1]+c-f[y][0];
			del.push_back(o);	h[x].push(o);
			sum[x]=sum[x]+o;
		}
	}
	
	upd1(x,max(0LL,num),add);
	f[x][0]=tot+sum[x];
	upd1(x,max(0LL,num-1),add);
	f[x][1]=tot+sum[x];
	for (LL u=0;u<add.size();u++) 
		h[x].push(add[u]),sum[x]+=add[u];
	for (LL u=0;u<del.size();u++) 
		h[x].del(del[u]),sum[x]-=del[u];
}
int main()
{
	scanf("%lld",&n);
	LL ans=0;
	for (LL u=1;u<n;u++)
	{
		LL x,y,c;
		scanf("%lld%lld%lld",&x,&y,&c);
		e[x].push_back({y,c});e[y].push_back({x,c});
		du[x]++;du[y]++;
		ans=ans+c;
	}
	printf("%lld ",ans);
	for (LL u=1;u<=n;u++)
	{
		vec[du[u]].push_back(u);
		sort(e[u].begin(),e[u].end(),cmp);
	}
	nxt[n]=n+1;
	for (LL u=n-1;u>=1;u--)
	{
		if (vec[u+1].size()) nxt[u]=u+1;
		else nxt[u]=nxt[u+1];
	}
	memset(vis,false,sizeof(vis));
	for (LL u=1;u<n;u++)
	{
		for (LL i=0;i<vec[u].size();i++) dele(vec[u][i]);
		ans=0;D=u;
		for (LL i=u+1;i<=n;i=nxt[i])	for (LL j=0;j<vec[i].size();j++)
		{
			if (vis[vec[i][j]]==true) continue;
			dfs(vec[i][j]);
			ans=ans+f[vec[i][j]][0];
		}
		for (LL i=u+1;i<=n;i=nxt[i])	for (LL j=0;j<vec[i].size();j++)	vis[vec[i][j]]=false;
		printf("%lld ",ans);
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值