[NOI Online 提升組]序列

傳送門

  不錯的題8~~(不知爲啥到我手上特判的賊多)~~c++

  簡述題意:給你$n$個結點,每一個結點有一個初始值$a_i$,以及目標值,而且給定兩種邊$(u_i,v_i)$,第一種邊使$a_{u_i}$和$a_{v_i}$同時加一,第二種邊使$a_{u_i}$和$a_{v_i}$一個加一,一個減一。問最後可否使全部結點變成目標狀態。git

  如下是我在當時測試時的思路。測試

  首先到達最終的目標,也就是說使得對應的結點的數值增長$b_i-a_i$,讓每一個結點表示這個值,問題就轉化成了可否使全部的數值變成$0$了。spa

  看兩種邊有什麼特別之處。一會兒看第一種邊沒發現什麼,第二種邊至關於能夠將一個結點上的部分數字「傳輸」到與其相鄰的結點之處,稍加拓展不難發現第二種邊構成的連通圖中,任意一個結點可將其數字傳到另外的任意一個結點,以下圖:code

  這樣的意義在於:能自由分配連通圖中全部的數值(都轉移到一個結點、或者分配到若干個點),能夠無需關心具體的分配狀況,因而將他們縮成一個點,其權值爲$\sum w_i$。blog

  這樣就只剩下第一種邊了。它們只有兩種狀況:(1)鏈接兩個連通塊(即縮點後鏈接兩個點),這個的影響是讓鏈接的兩個縮點加或減相等的數字;(2)在某個連通塊內,這個的影響是讓這個縮點加或減一個偶數。咱們仍是用傳輸的思想,手算會發現(1)能夠將某個點的數值隔兩個點傳過去,以下圖:get

  對於縮過點的圖,每一個由第一種邊構成的連通塊,仔細想一想會發現:若是這個連通塊不能被黑白染色,則必定能夠將數值放到某一個奇環上的一個點,一樣這個也是可逆的,故一個數必定能夠藉助奇環把另外一個相等的數消除;反之,若是被黑白染色,顯然能夠將全部黑點的數值和白點的數值移到一條相鄰的邊,而後再一塊兒消掉。沒有(2)狀況的干擾下,對於每一個連通塊,按照上面分類討論,若是仍然不能徹底消除,那顯然輸出$\text{NO}$了。全部的都知足才輸出$\text{YES}$。it

  加上(2),其實就是能夠對權值調整,在有(2)的連通塊下,不必定要所有消除,只要消到能剩餘$2$的倍數就好啦(能夠依靠這樣的邊在連通塊內部就消除掉)。因而咱們整理一下:class

  一、根據第二種邊求出全部的連通塊而且縮起來;im

  二、根據第一種邊求出全部的連通塊,對每一個連通塊判斷可否黑白染色,若是能就染色,求出全部黑點和白點的權值和,順便看看有沒有狀況(2);

  三、檢查該連通塊是否知足要求:a.若是不能黑白染色,只須要看這個連通塊的權值和是否是偶數;b.若是能,判斷黑點權值和和白點權值和的關係,若是有(2),那麼只需差爲偶數便可;不然必須相等(具體詳見上文);

  四、若是全部的連通塊都能經過測試,輸出$\text{YES}$;不然輸出$\text{NO}$。

  時間複雜度$\text{O}(Tn)$。能夠經過。

#include <bits/stdc++.h>

#define ll long long
#define ull unsigned long long
#define min(a, b) (a) < (b) ? (a) : (b)
#define max(a, b) (a) > (b) ? (a) : (b)
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

inline int read() {
	int w = 0, f = 1; char c;
	while (!isdigit(c = getchar())) c == '-' && (f = 1);
	while (isdigit(c)) w = w*10+(c^48), c = getchar();
	return w * f;
}

const int maxn = 114514;

int n, m;
std::vector<int> G1[maxn], G2[maxn], G[maxn];
int id[maxn], delta[maxn], tag[maxn], col[maxn], idcnt = 0;
// id爲連通塊編號、delta表示a[i]-b[i](相反亦可),tag表示有沒有(2)狀況,col爲染色狀況,idcnt表示連通塊個數
ll sum[maxn]; // 每一個連通塊內的權值總和

void dfs(int u) {
	sum[id[u] = idcnt] += delta[u]; // 標記連通塊而且合併權值
	rep0(i, G2[u].size()) if (!id[G2[u][i]]) dfs(G2[u][i]);
}

int paint(int u, ll &white, ll &black, int &flag) {
	int ok = 1; // 表示是否染色成功
	col[u] == 1 ? white += sum[u] : black += sum[u]; flag |= tag[u]; // 計算white,black點的權值
	rep0(i, G[u].size()) {
		int v = G[u][i];
		if (col[v]) { if (col[v] == col[u]) ok = 0; continue; } // 失敗
		col[v] = 3-col[u];
		ok &= paint(v, white, black, flag);
	}
	return ok;
}

int main() {
	for (int T = read(); T; T--) {
		n = read(), m = read();
		rep(i, 1, n) delta[i] = read();
		rep(i, 1, n) delta[i] -= read();
		rep(i, 1, n) G1[i].clear(), G2[i].clear(); // 清空
		rep(i, 1, m) {
			int t = read(), u = read(), v = read();
			if (t == 1) G1[u].push_back(v), G1[v].push_back(u);
			if (t == 2) G2[u].push_back(v), G2[v].push_back(u);
		}
		memset(id, 0, sizeof id); idcnt = 0;
		rep(i, 1, n) if (!id[i]) G[++idcnt].clear(), sum[idcnt] = tag[idcnt] = col[idcnt] = 0, dfs(i); // 初始化+標記
		rep(u, 1, n)
			rep0(i, G1[u].size()) {
				int v = G1[u][i];
				if (id[u] == id[v]) tag[id[u]] = 1; else G[id[u]].push_back(id[v]);
			}
		int ans = 1;
		rep(i, 1, idcnt) {
			if (col[i]) continue; // 染過色直接跳過
			ll white = 0, black = 0; int flag = 0;
			col[i] = 1;
			if (paint(i, white, black, flag)) { // 染色成功
				if (flag) { if ((white ^ black) & 1) { ans = 0; break; } } // 有(2)狀況判斷差是否爲偶數
				else if (white != black) { ans = 0; break; } // 沒有就判斷是否相等
			} else if ((white ^ black) & 1) { ans = 0; break; } // 不成功判斷差是否爲偶數
		}
		printf("%s\n", ans ? "YES" : "NO");
	}
	return 0;
}
相關文章
相關標籤/搜索