前言
模拟赛中因为判无解判错了错一个点再次喜提 \(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 anif(v2[x] == 1) v1[b[i].to].push_back(b[i].z-d[x]);//yi ge hui jian de shuelse 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 defor(int z = 0;z < V1[x].size();z++) mx = max(mx,-V1[x][z]),mi = min(mi,a[x]-V1[x][z]);//hui jia deif(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,miif(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;
}