题解:P3544 [POI 2012] BEZ-Minimalist Security

前言

模拟赛中因为判无解判错了错一个点再次喜提 \(0\) 分。

思路

不难想到,对于一个联通块,确定一个点的值,这个联通块其它的点的值都确定了,当然无法确定就是无解。

考虑优化上诉过程,容易发现合法的取值一定是一段区间,我们想做到在 \(i\) 最终值为 \(0\) 时,最少加多少会合法,最多加多少会合法,我们二分图染色,与 \(i\) 颜色一样的记为 \(+1\),不一样的记为 \(-1\),其和就是每次 \(i\) 值增加会新增的值。

我们给每个点记录两个 \(\verb!vector!\),分别表示一些会增加的量和一些会减少的量,举个例子,我们设 \(b_i\) 为颜色,我们设根节点颜色为 \(1\),在 \(b_i = 1\) 时,与 \(i\) 相邻的点 \(j\) 边权为 \(z\)\(j\) 就会加入一个会减少的量 \(z-a_i\)\(b_i = 0\) 就加入一个会减的,可以理解为插入一条直线。

我们现在来考虑每个点会造成的影响,如果它又有斜率为正的直线又有斜率为负的直线,就看一下是否存在一个时刻,所有直线交于一点,没有就无解。

同时对于每一条直线,我们要找出它在那一段区间合法,然后更新左右端点,最后在求答案时,把无解判掉即可,详情可以看题解,有注释。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 5e5+10,M = 3e6+10;
int n,m,a[N],head[N],d[N],v[N],v2[N],t[2],st[N],cnt,x,y,z,l,r,mid,ans,ans1,sum2,sum3,sum,sum1,mi,mx,op,op1,op2;
vector<int>v1[N],V1[N];
/*
v1:存储斜率为正的直线
V1:存储斜率为负的直线
t_i:颜色i出现几次
st:联通快出现的点 
*/ 
struct w1
{
	int to,nxt,z;
}b[M<<1];
inline void add(int x,int y,int z)
{
	b[++cnt].nxt = head[x];
	b[cnt].to = y,b[cnt].z = z;
	head[x] = cnt;
}
void dfs(int x,int y)
{
	v[x] = 1; v2[x] = !v2[y]; st[++cnt] = x; t[v2[x]]++; sum2 += d[x];
	for(int i = head[x];i;i = b[i].nxt)
	{
		if(!v[b[i].to]) d[b[i].to] = b[i].z-d[x],dfs(b[i].to,x);//dfs gen xin da an
		if(v2[x] == 1) v1[b[i].to].push_back(b[i].z-d[x]);//yi ge hui jian de shu
		else V1[b[i].to].push_back(b[i].z-d[x]);//yi ge hui jia de shu
	}
}
signed main()
{
//	freopen("add.in","r",stdin);
//	freopen("add.out","w",stdout);
	read(n),read(m);
	for(int i = 1;i <= n;i++) read(a[i]),sum3 += a[i];
	for(int i = 1;i <= m;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z); 
	for(int i = 1;i <= n;i++)
		if(!v[i])
		{
			d[i] = cnt = 0;
			mi = 1e16,mx = 0,op = -1e16; t[0] = t[1] = 0; sum2 = 0;
			V1[i].push_back(0); dfs(i,0);
			for(int j = 1;j <= cnt;j++) 
			{
				x = st[j];
				for(int z = 0;z < v1[x].size();z++) mx = max(mx,v1[x][z]-a[x]),mi = min(mi,v1[x][z]);//hui jian de
				for(int z = 0;z < V1[x].size();z++) mx = max(mx,-V1[x][z]),mi = min(mi,a[x]-V1[x][z]);//hui jia de
				if(v1[x].size() != 0 && V1[x].size() != 0)
				{
					op1 = -1e16;
					for(int z = 0;z < v1[x].size();z++)
					{
						if(op1 == -1e16) op1 = v1[x][z];
						else if(op1 != v1[x][z]){ op1 = -1e17; break; }
					}
					if(op1 == -1e17) { op == -1e17; break; }//1:多个斜率相同的直线且初值不一样,永不相交 
					op2 = -1e16;
					for(int z = 0;z < V1[x].size();z++)
					{
						if(op2 == -1e16) op2 = V1[x][z];
						else if(op2 != V1[x][z]){ op2 = -1e17; break; }
					}//同上 || 两个无法某一刻相同 || 上升的初值比下降的初值大,不相交 
					if(op2 == -1e17 || (op1-op2)%2==1 || op2 > op1) { op = -1e17; break; }
					if(op == -1e16 || op == (op1-op2)/2) op = (op1-op2)/2;
					else { op == -1e17; break; }//2:多个必须选的点,无解 
				}
			}//mx,op,mi
			if(op == -1e17) { printf("NIE"); return 0; } 
			if(mx > mi) { printf("NIE"); return 0; }//3:左端点大于右端点,无解 
			if(op != -1e16 && (mx > op || op > mi)) { printf("NIE"); return 0; }//4:必须选的点不在区间里面,无解 
			if(op != -1e16) ans = ans1 = op*(t[1]-t[0])+sum2;//选取的点确定了 
			else ans = mi*(t[1]-t[0])+sum2,ans1 = mx*(t[1]-t[0])+sum2;//最大值和最小值一定是两个端点,哪个最大哪个最小不重要,加的时候取min,max就好了 
			sum += max(ans,ans1),sum1 += min(ans,ans1);
		}
	print(sum3-sum),pc(' '),print(sum3-sum1),pc('\n');
	flush();
	return 0;
}
posted @ 2025-02-16 12:17  kkxacj  阅读(7)  评论(0)    收藏  举报