1. 程式人生 > 實用技巧 >洛谷P5406 [THUPC2019]找樹(FWT)

洛谷P5406 [THUPC2019]找樹(FWT)

洛谷P5406 [THUPC2019]找樹(FWT)

題目大意

定義 \(\otimes_1, \otimes_2, \otimes_3\) 分別為按位與、按位或、按位異或運算。記 \(a_i\) 表示 \(a\) 的從低位到高位的第 \(i\) 個二進位制位。定義一個作用在 \(w\) 位二進位制數上的新運算 \(\oplus\),滿足對於結果 \(a\oplus b\) 的每一位 \((a\oplus b)_i\)\((a\oplus b)_i = a_i \otimes_{\large o_i} b_i\)。不難驗證 \(\oplus\) 運算滿足結合律和交換律。

給出一張 \(n\)

個點 \(m\) 條邊的無向圖,每一條邊的權值是一個 \(w\) 位二進位制數(即小於 \(2^w\) 的非負整數)。請你找一棵原圖的生成樹。設你找出的生成樹中的邊邊權分別為 \(v_1,\cdots,v_{n-1}\),請你最大化 \(v_1\oplus v_2\oplus\cdots\oplus v_{n-1}\)

資料範圍

接下來 \(m\) 行,每一行三個非負整數 \(x,y,v\),表示一條連線 \(x\)\(y\) 權值為 \(v\) 的邊,保證 \(1\leq x,y\leq n\)\(0\le v < 2^w\)

對於所有資料,\(1\le n\le 70,1\le m\le 5000,1\le w \le 12\)

來自 THUPC(THU Programming Contest,清華大學程式設計競賽)2019。

題解等資源可在 https://github.com/wangyurzee7/THUPC2019 檢視。

解題思路

大佬做題不證明,蒟蒻看著很難受,因此寫一下這篇題解給一些簡單的正確性證明

神仙題,首先這題不是最優化題而是數數題,我們算出權值為 k 的生成樹個數,如果不為零就可能是答案

生成樹計數就看矩陣樹定理,矩陣樹定理在邊權為環的情況下成立,而集合冪級數構成了一個環,加法就是對應相加,乘法就是卷積

先假設所有運算子都是異或

求解行列式我們直接暴力階乘演算法,發現只含有乘法和加法,乘法的時候又是先 FWT 一遍然後點值對應相乘最後再加起來,我們發現每個點值互不影響,所以我們直接分別對每個點值求行列式,這樣就可以高斯消元了

現在運算子更加的豐富,你可以將 FWT 看成將 \(2^n\) 個向量代入求得點值的過程,那麼每一位又是獨立的,所以我們對應的算用對應的 FWT 即可,看程式碼就知道具體實現了,如果想要理解為什麼是這樣的可以看看我這篇文章,或者去翻論文,雖然也不是很好懂(不知道為什麼 UOJ 自測除錯 1.7s,loj 卻要 4s 都不夠

#pragma GCC optimize(2)
#include <queue>
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define ll long long
#define fi first
#define se second
using namespace std;

template <typename T>
void read(T &x) {
    x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=x*10+(c^48);
    if (f) x=-x;
}

template<typename F>
inline void write(F x, char ed = '\n')
{
	static short st[30];short tp=0;
	if(x<0) putchar('-'),x=-x;
	do st[++tp]=x%10,x/=10; while(x);
	while(tp) putchar('0'|st[tp--]);
	putchar(ed);
}

template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y); }

template <typename T>
inline void Mn(T &x, T y) { x > y && (x = y); }

const int N = 75;
const int Ww = 5048;
const int P = 998244353;
const int inv2 = (P + 1) / 2;
ll e[N][N][Ww], w, n, m, W;
char op[N];
void exFwt(ll *f, int ty) {
	for (int i = 1, t = 1;t <= w; t++, i <<= 1) 
		for (int j = 0;j < W; j += (i << 1)) 
			for (int k = 0;k < i; k++) {
				if (op[t] == '|') f[i+j+k] = (f[i+j+k] + ty * f[j+k] + P) % P;
				else if (op[t] == '&') f[j+k] = (f[j+k] + ty * f[i+j+k] + P) % P;
				else {
					ll x = f[j+k], y = f[i+j+k];
					f[j+k] = (x + y) % P, f[i+j+k] = (x - y + P) % P;
					if (ty == -1) f[j+k] = f[j+k] * inv2 % P,
								f[i+j+k] = f[i+j+k] * inv2 % P;
				}
			}
}

ll M[N][N];

ll fpw(ll x, ll mi) {
	ll res = 1;
	for (; mi; mi >>= 1, x = x * x % P)
		if (mi & 1) res = res * x % P;
	return res;
}

ll Mat(void) {
	ll ans = 1;
	for (int i = 1;i < n; i++) {
		for (int j = i;j < n; j++) {
			if (M[j][i]) {
				if (j == i) break;
				ans = P - ans;
				for (int k = i;k < n; k++) swap(M[i][k], M[j][k]);
				break;
			}
		}
		ans = ans * M[i][i] % P;
		if (!ans) return ans; ll inv = fpw(M[i][i], P - 2);
		for (int j = i;j < n; j++) M[i][j] = M[i][j] * inv % P;
		for (int j = i + 1;j < n; j++) {
			ll t = P - M[j][i];
			for (int k = i;k < n; k++)
				M[j][k] = (M[j][k] + t * M[i][k]) % P;
		}
	}
	return ans;
}

ll f[Ww];
int main() {
	read(n), read(m), scanf ("%s", op + 1);
	w = strlen(op + 1); W = 1 << w;
	for (int i = 1, x, y, v;i <= m; i++) {
		read(x), read(y), read(v);
		e[x][y][v]--, e[y][x][v]--;
		e[x][x][v]++, e[y][y][v]++;
	}
	for (int i = 1;i <= n; i++)
		for (int j = 1;j <= n; j++)
			exFwt(e[i][j], 1);
	for (int i = 0;i < W; i++) {
		for (int j = 1;j <= n; j++) 
			for (int k = 1;k <= n; k++)
				M[j][k] = e[j][k][i];
		f[i] = Mat(); 
	}
	exFwt(f, -1);
	for (int i = W - 1;i >= 0; i--)
		if (f[i] != 0) return write(i), 0;
	return write(-1), 0;
}