AT3913-XOR Tree【狀壓dp】
阿新 • • 發佈:2021-12-18
正題
題目連結:https://www.luogu.com.cn/problem/AT3913
題目大意
給出一棵有邊權的樹,你每次可以選擇一條鏈讓所有的邊異或上同一個值,求最少的操作次數使得所有邊的權值都為\(0\)。
\(2\leq n\leq 10^5,0\leq w<16\)
解題思路
一條邊的權值可以視為連線的兩個點的權值異或,那麼我們就可以把邊邊為點權了。
而鏈的異或就可以變成首尾兩個點同時異或上一個值。
那麼問題就變為了給\(n\)個數字你每次可以讓兩個同時異或上任意一個數,求最少操作次數使得它們都變為\(0\)。
那麼顯然一樣的我們直接異或掉最優,那麼現在就只剩下最多\(16\)個不同的數了。
考慮狀壓\(dp\),注意到如果一個集合能夠操作到\(0\)那麼這個集合肯定有所有數字的異或和為\(0\),因為無論怎麼操作序列的異或和都不會變。
那麼理論上如果有\(x\)個數字,那麼我們的操作次數上限是\(x-1\),但是如果我們能把集合\(S\)分成兩個集合\(T,S-T\)且這兩個集合的異或和都為\(0\),那麼就變成了\(x-2\),也就是最小的值在我們儘量分最多集合得到。
設\(f_S\)表示\(S\)最多分成多少個集合,然後\(O(3^{16})\)轉移就好了。
時間複雜度:\(O(n+3^{16})\)
code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1e5+10,M=16; int n,ans,a[N],v[M],c[1<<M],f[1<<M]; int main() { scanf("%d",&n); for(int i=1,x,y,w;i<n;i++){ scanf("%d%d%d",&x,&y,&w); a[x]^=w;a[y]^=w; } for(int i=0;i<n;i++)v[a[i]]++; int MS=(1<<16); memset(f,0xcf,sizeof(f)); for(int s=1;s<MS;s++) for(int i=0;i<16;i++) if((s>>i)&1){ c[s]=c[s-(1<<i)]^i; if(!c[s])f[s]=0; break; } int S=0;f[0]=0; for(int i=1;i<16;i++) ans+=(v[i]+1)/2,S|=((v[i]&1)<<i); if(!S)return printf("%d\n",ans)&0; for(int s=1;s<MS;s++){ if(c[s])continue; for(int t=(s-1)&s;t>=(s^t);t=(t-1)&s) f[s]=max(f[s],f[t]+f[s^t]+1); } printf("%d\n",ans-f[S]-1); return 0; } /* 4 0 1 5 1 2 3 2 3 5 */