【2021 HDU多校集训第三场】10. Road Discount

Description

给你一个n个点和m条边,每条边有两种权值:黑权值和白权值,
你需要构建一棵连接这n个点的生成树,使得这n-1条边,有不超过k条边用白权值,其他的用黑权值。

对于每个 k ∈ [ 0 , n − 1 ] k\in [0,n-1] k[0,n1]你都需要求出答案。
n ≤ 1000 , m ≤ 200000 , 边 权 ≤ 1000 n\leq 1000, m\leq 200000, 边权\leq 1000 n1000,m200000,1000

Solution

题意相当于:有2m条边,你需要构建一棵生成树,使得选的边中有不超过k条白边,剩下的选黑边,

我们从小到大枚举k,每次选若干条白边,顶掉若干条黑边。
显然的,白边一旦被选上就不会再被顶掉了。所以我们对所有的白边做一次生成树,被选上的白边才是有用的。

我们并不好直接控制选多少条白边,考虑给每个白边全部加上一个权值c,当c很大时我们选择的白边就会很少,反之亦然。
我们发现这本质上就是每次选择一条白边(x,y),把原生成树上x到y路径上权值最大的边给顶掉,
这个c其实就是顶掉以后,生成树减少的权值。

从大到小枚举c,顶掉的黑边就会越来越多,换句话说:我们是在贪心的顶掉黑边,每次选能最大程度减少权值的白边。

如果出现c-=1以后一下子选了一大堆白边,那说明这一堆白边对权值的减少量都是相同的,选哪一条答案都一样。

所以我们做的时候,就每次看看能不能多选一条白边,如果可能就计算答案,不可以就c-=1,直到可以。

复杂度: O ( n 2 ) O(n^2) O(n2)

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define efo(i,q) for(int i=0;i<(int)B[q].size();++i)
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N=1500,M=200500,INF=1e9+7;

#define S 1000000
char bf[S], *p1 = bf, *p2 = bf;
#define nc() (p1==p2&&(p2=(p1=bf)+fread(bf,1,S,stdin),p2==p1)?-1:*p1++)

inline int read(int &x) {
    x = 0;
    char ch = nc();
    for (; ch < '0' || ch > '9'; ch = nc());
    for (; ch <= '9' && ch >= '0'; x = x * 10 + ch - 48, ch = nc());
    return x;
}

unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
mt19937 rand_num(seed);  // 大随机数
int RD(int q){return rand_num()%q;}


int n,m,ans;
int g[N];
int gf(int q){return g[q]==q?q:g[q]=gf(g[q]);}
struct Val
{
	int x,y,v;
}sc[M],sc1[M];
bool z[M];

int Ans[N];
int doit(int e,int AD)
{
	int cnt0=0,cnt1=0,q,w;
	int ans=0;
	fo(i,1,n)g[i]=i;
	for(int i=1,j=1;cnt0+cnt1<n-1;)
	{
		if(sc[i].v>sc1[j].v+AD)
		{
			q=sc1[j].x,w=sc1[j].y;
			if(gf(q)!=gf(w)&& (cnt1<e||z[j]))
			{
				ans+=sc1[j].v;
				g[gf(q)]=g[w];
				if(!z[j])
				{
					z[j]=1;
					++cnt1;
				}else ++cnt0;
			}
			++j;
		}else{
			q=sc[i].x,w=sc[i].y;
			if(gf(q)!=gf(w))
			{
				ans+=sc[i].v;
				g[gf(q)]=g[w];
				++cnt0;
			}
			++i;
		}
	}
	return cnt1==e?ans:INF;
}
void init()
{
	int q,w,e;
	read(n),read(m);
	ans=0;
	fo(i,1,m)
	{
		read(q),read(w),read(e);
		sc[i]={q,w,e};
		read(e);
		sc1[i]={q,w,e};
		z[i]=0;
	}
	sort(sc+1,sc+1+m,[](Val q,Val w){return q.v<w.v;});
	sort(sc1+1,sc1+1+m,[](Val q,Val w){return q.v<w.v;});

	fo(i,1,n)g[i]=i;
	int cnt=0;
	fo(i,1,m)
	{
		q=sc[i].x,w=sc[i].y;
		if(gf(q)!=gf(w))
		{
			ans+=sc[i].v;
			g[gf(q)]=g[w];

			++cnt;
			swap(sc[i],sc[cnt]);
		}
	}
	sc[n].v=INF;
	fo(i,1,n)g[i]=i;
	cnt=0;
	fo(i,1,m)
	{
		q=sc1[i].x,w=sc1[i].y;
		if(gf(q)!=gf(w))
		{
			g[gf(q)]=g[w];
			
			++cnt;
			swap(sc1[i],sc1[cnt]);
		}
	}
	sc1[n].v=INF;
	Ans[0]=ans;
	int AD=1000;

	fo(i,1,n-1)
	{
		ans=INF;
		for(;AD>=0&&(ans=doit(1,AD))==INF;--AD);
		Ans[i]=min(Ans[i-1],ans);
	}

	fo(i,0,n-1)printf("%d\n",Ans[i]);
	
}
int main()
{
	int q,w,_;
	read(_);
	while(_--)
	{
		init();
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值