YbtOj#20073. 「NOIP2020 模拟赛 B 组 Day6」钻石守卫

R e s u l t Result Result

...


H y p e r l i n k Hyperlink Hyperlink

http://noip.ybtoj.com.cn/contest/105/problem/3


D e s c r i p t i o n Description Description

一张无向图 G { n , m } G\{n,m\} G{n,m},给定点权和边权,现在要求降低这张图的点权,使得任意一条边权等于它连接的两个点的点权和

求最小/大的点权和

数据范围: n ≤ 5 × 1 0 5 , m ≤ 3 × 1 0 6 n\leq 5\times 10^5,m\leq 3\times 10^6 n5×105,m3×106


S o l u t i o n Solution Solution

最小点权和=点权和-最大可删除点权
最大点权和=点权和-最小可删除点权

考虑求删除点权的范围即可

假设我们选定一个点 a a a,它的权值是 x x x,现在有二条边 a − > b − > c a->b->c a>b>c,边权分别为 d i , d j d_i,d_j di,dj,设 b , c b,c b,c的权值为 y , z y,z y,z

则有 0 ≤ d i − x ≤ y 0\leq d_i-x\leq y 0dixy移项可以得到 y y y的取值范围
同理,有 0 ≤ d j − d i + x ≤ z 0\leq d_j-d_i+x\leq z 0djdi+xz移项可以得到 z z z的取值范围

容易发现, a a a可以到达的点(即同一个联通块内),每个点的取值范围都是关于 x x x的一次函数 f ( x ) = k x + b f(x)=kx+b f(x)=kx+b,显然我们讨论 k k k即可得到他们的取值范围,最后取它们解集的交集即可

需要注意的是,如果构成了环,那么会得到方程 k 1 x + b 1 = k 2 x + b 2 k_1x+b_1=k_2x+b_2 k1x+b1=k2x+b2,其中 k 1 , b 1 k_1,b_1 k1,b1是你通过这条返祖边(构成环的那条边)得到的 k k k b b b k 2 , b 2 k_2,b_2 k2,b2是这个点之前得到的 k k k b b b
讨论它解的情况即可

具体地,若 k 1 = k 2 k_1=k_2 k1=k2,当且仅当 b 1 = b 2 b_1=b_2 b1=b2时该方程有无限组解,否则无解
无限组解说明这个点的权值随便改,那我们可以不用管它,无解返回0即可
否则,则方程存在根 x = b 2 − b 1 k 1 − k 2 x=\frac {b_2-b_1}{k_1-k_2} x=k1k2b2b1,判断这玩意儿是不是整数即可

时间复杂度: O ( n + m ) O(n+m) O(n+m)


C o d e Code Code
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 500010
using namespace std;int n,m,p[N],l[N],tot,u,v;
struct node{int next,to;LL w;}e[6000010];LL w;
inline void add(int u,int v,LL w){e[++tot]=(node){l[u],v,w};return (void)(l[u]=tot);return;}
LL sum,b[N],sumb,L,R,maxs,mins;
int k[N],sumk;
inline bool ck(int K1,LL B1,int K2,LL B2)
{
	if(K1==K2) return B1==B2;
	if((B2-B1)%(K1-K2)) return false;
	LL w=(B2-B1)/(K1-K2);
	if(w<L||w>R) return false;
	return (L=R=w,true);
}
inline bool dfs(int x)
{
	sumk+=k[x];sumb+=b[x];
	if(k[x]==1) L=max(L,-b[x]),R=min(R,p[x]-b[x]);
	else L=max(L,b[x]-p[x]),R=min(R,b[x]);
	if(L>R) return 0;
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(k[y]) {if(ck(-k[x],e[i].w-b[x],k[y],b[y])==0) return false;}
		else {k[y]=-k[x];b[y]=e[i].w-b[x];if(dfs(y)==0) return false;}
	}
	return 1;
}
inline LL read()
{
	LL d=1,f=0;char c;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
signed main()
{
	freopen("diamond.in","r",stdin);
	freopen("diamond.out","w",stdout);
	n=read();m=read();
	for(register int i=1;i<=n;i++) p[i]=read(),sum+=p[i];
	for(register int i=1;i<=m;i++) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
	for(register int i=1;i<=n;i++) if(k[i]==0)
	{
		L=sumk=sumb=0;R=p[i];k[i]=1;
		if(dfs(i)==false) return puts("NIE\n")&0;
		else
		{
			if(sumk<0) maxs+=L*sumk+sumb,mins+=R*sumk+sumb;
			else maxs+=R*sumk+sumb,mins+=L*sumk+sumb;
		}
	}
	printf("%lld %lld\n",sum-maxs,sum-mins);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值