Educational Codeforces Round 122 (Rated for Div. 2) E. Spanning Tree Queries(最小生成树 分段一次函数 暴力+离线)

该篇文章介绍了如何使用离线算法解决一个图论问题,即给定一个n个点m条边的无自环和重边图,对q次询问进行处理,每次询问指定边权为整数x与边权重之绝对差时,求最小生成树的总权值。关键在于构造一次函数并利用双指针技巧优化查询过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

n(n<=50)个点m(m<=300)条边保证联通的图,没有自环和重边

q(q<=1e7)次询问,每次给定一个整数x(x<=1e8),

询问当m条边的边权是abs(wi-x)时,图的最小生成树的值是多少

允许离线,最后只需将总共q个询问的答案异或在一起,输出最终的异或值

思路来源

jiangly代码

题解

当时赛中a了,但是写的很麻烦,现在补一下这个一次函数的写法

m条边枚举两两中点,C(m,2)+m个临界值变化的位置,去不去重无所谓

这m^2临界位置构成了若干个一次函数直线,询问两个临界为之间的时候,视为落在左边的直线上

那么就离线一下询问,双指针过一下这m^2个临界位置和q个询问,

对于每个询问x,找到不超过x的最大临界位置,

对其暴力做最小生成树求权值即可,

做好之后是一个形如y=coef*x+sum的形式,也就是一次函数y=kx+b

这个两年前没太想明白,现在看了看感觉差不多

考虑大于的值的贡献是w-x[now],小于的贡献是x[now]-w,

其中x[now]是临界位置,q[i]是询问的值,q[i]严格大于x[now],且严格小于x[now+1]

那么x固定时,每一条边都是对应有(k∈(-1,1),b)的一个贡献,线性相加即可

那么由于q[i]>x[now],所以当x[now]处,生成树有多个时,

需要选择coef最小的那个,才能使得一次函数在q[i]处的值最小

有个没弄懂的地方,是中点只能是(e[i].w+e[j].w+1)/2,也就是向上取整

改成(e[i].w+e[j].w)/2就wa了

大概感性理解了一下,感觉是左闭右开的直线形式,

代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<ll,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define scll(a) scanf("%lld",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=55,M=305,S=M*M,K=1e7+10;
int t,n,m,par[N],x[S],cnt,q[K];
int p,k,a,b,c;
ll ans;
struct edge{
	int u,v,w;
	void rd(){
		sci(u),sci(v),sci(w);
	}
}e[M];
int find(int x){
	return par[x]==x?x:par[x]=find(par[x]);
}
int main(){
	sci(n),sci(m);
	rep(i,1,m)e[i].rd();
	x[cnt++]=0;
	rep(i,1,m){
		rep(j,i,m){
			x[cnt++]=(e[i].w+e[j].w+1)/2;
		}
	}
	sort(x,x+cnt);
	scanf("%d%d%d%d%d",&p,&k,&a,&b,&c);
	rep(i,0,p-1){
		sci(q[i]);
	}
	rep(i,p,k-1){
		q[i]=(1ll*q[i-1]*a+b)%c;
	}
	sort(q,q+k);
	int now=-1;
	ll coef=0,sum=0;
	rep(i,0,k-1){
		int las=now;
		while(now+1<cnt && x[now+1]<=q[i]){
			now++;
		}
		//printf("i:%d q:%d las:%d now:%d\n",i,q[i],las,now);
		if(las<now){
			coef=sum=0;
			rep(j,1,n)par[j]=j;
			sort(e+1,e+m+1,[&](edge &u,edge &v){
				int w1=abs(u.w-x[now]),w2=abs(v.w-x[now]);
				if(w1^w2)return w1<w2;
				else return u.w>v.w; // w1=w2时 在x[now]处贡献相同 但是选大的w coef更小 一次函数y=kx+b x增至q[i]时 k减小 y减小
			});
			rep(j,1,m){
				int u=find(e[j].u),v=find(e[j].v);
				if(u==v)continue;
				par[v]=u;
				int w=e[j].w;
				//w>x[now] +w-x[now]
				//w<=x[now] +x[now]-w
				//printf("i:%d j:%d u:%d v:%d w:%d\n",i,j,u,v,w);
				if(w>x[now]){//q[i]介于x[now]与x[now+1]间 有多少个比x[now]大的,就是有多少个比q[i]大的,所以这么求coef
					coef--;
					sum+=w;
				}
				else{
					coef++;
					sum-=w;
				}
			}
		}
		//printf("i:%d q:%d coef:%lld sum:%lld add:%lld\n",i,q[i],coef,sum,coef*q[i]+sum);
		ans^=coef*q[i]+sum;
	}
	ptlle(ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值