洛谷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\) 行,每一行三個非負整數 \(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;
}