【BZOJ2322】[BeiJing2011]夢想封印 高斯消元求線性基+DFS+set
【BZOJ2322】[BeiJing2011]夢想封印
Description
漸漸地,Magic Land上的人們對那座島嶼上的各種現象有了深入的了解。
為了分析一種奇特的稱為夢想封印(Fantasy Seal)的特技,需要引入如下的概念:
每一位魔法的使用者都有一個“魔法脈絡”,它決定了可以使用的魔法的種類。
一般地,一個“魔法脈絡”可以看作一個無向圖,有N個結點及M條邊,將結點編號為1~N,其中有一個結點是特殊的,稱為核心(Kernel),記作1號結點。每一條邊有一個固有(即生成之後再也不會發生變化的)權值,是一個不超過U的自然數。
每一次魔法驅動,可看作是由核心(Kernel)出發的一條有限長的道路(Walk),可以經過一條邊多次,所驅動的魔法類型由以下方式給出:
將經過的每一條邊的權值異或(xor)起來,得到s。
如果s是0,則驅動失敗,否則將驅動編號為s的魔法(每一個正整數編號對應了唯一一個魔法)。
需要註意的是,如果經過了一條邊多次,則每一次都要計入s中。
這樣,魔法脈絡決定了可使用魔法的類型,當然,由於魔法與其編號之間的關系尚未得到很好的認知,此時人們僅僅關註可使用魔法的種類數。
夢想封印可以看作是對“魔法脈絡”的破壞:
該特技作用的結果是,“魔法脈絡”中的一些邊逐次地消失。
我們記總共消失了Q條邊,按順序依次為Dis1、Dis2、……、DisQ。
給定了以上信息,你要計算的是夢想封印作用過程中的效果,這可以用Q+1個自然數來描述:
Ans0為初始時可以使用魔法的數量。
Ans1為Dis1被破壞(即邊被刪去)後可以使用魔法的數量。
Ans2為Dis1及Dis2均被破壞後可使用魔法的數量。
……
AnsQ為Dis1、Dis2、……、DisQ全部被破壞後可以使用魔法的數量。
Input
第一行包含三個正整數N、M、Q。
接下來的M行,每行包含3個整數,Ai、Bi、Wi,表示一條權為Wi的與結點Ai、Bi關聯的無向邊,其中Wi是不超過U的自然數。
接下來Q行,每行一個整數:Disi。
Output
一共包Q+1行,依次為Ans0、Ans1、……、AnsQ。
Sample Input
【輸入樣例1】3 3 2
1 2 1
2 3 2
3 1 4
1
3
【輸入樣例2】
5 7 7
1 2 1
1 3 1
2 4 2
2 5 2
4 5 4
5 3 9
4 3 1
7
6
5
4
3
2
1
Sample Output
【輸出樣例1】5
2
0
【樣例1解釋】
初始時可使用編號為1、3、4、6、7的魔法。
在刪去第1條邊(連結1、2結點的邊)後,可使用4和6號魔法。
第3條邊(連結第1、3結點的邊)也被刪去後,核心(Kernel)即結點1孤立,易知此時無法使用魔法。
【輸出樣例2】
15
11
5
2
2
1
1
0
HINT
【數據規模和約定】
所有數據保證該無向圖不含重邊、自環。
所有數據保證不會有一條邊被刪除多次,即對於不同i和j,有Disi≠Disj
30%的數據中N ≤ 50,M ≤ 50,Q ≤50,U≤100;
60%的數據中N ≤ 300,M ≤ 300,Q ≤50,U≤10^9;
80%的數據中N ≤ 300,M ≤ 5000,Q ≤5000,U≤10^18;
100%的數據中N ≤ 5000,M ≤ 20000,Q ≤20000,U≤10^18;
題解:又一道神題
我們回憶2155那道題的做法:所有從1到一個點的路徑的異或和 都可以表示成 任意一條從1到該節點的路徑 和 某些簡單環 的異或和。
那麽本題變成了動態求異或和的種類數,我們首先的思路就是將刪邊變成倒著往圖中加邊。然後思考一下我們具體都需要維護些什麽。
從答案的角度分析,答案=(所有本質不同的路徑數)*2^(本質不同的環的個數)-1,註意本質不同的路徑包括哪裏都不走,所以最後要-1。具體本質不同是什麽意思呢?
回憶2155那道題,本質不同的環的意思就是:將所有環高斯消元後得到的線性基,因為這樣就可以表示任意環的異或和。
那麽本質不同的路徑的意思也就出來了,我們最終得到的不簡單的路徑的異或和=任意一條簡單路徑的異或和^任意一些環的異或和,所以兩條簡單路徑本質不同當且僅當它們用線性基消元後,得到的值不同。具體地,我們可以用set來維護這樣的路徑。
好了,分析了這麽多,現在我們終於可以得出一個可行的做法了:當加入邊(a,b)時:
1.若a,b都已經被訪問過(或者說加入的是一條非樹邊),我們此時得到了一個簡單環,將這個簡單環的異或和放到線性基中消元,如果沒有消成0,那就說明我們應該將這個圓加入到線性基中去,直接把它加入到線性基中對應的位置就行了。但是註意一點,更改線性基後,之前的路徑並沒有被這條邊消元,所以要把set裏的邊一個一個取出來,重新消元再塞回去。
2.若a被訪問過b沒有被訪問過(或者說加入了一條樹邊),我們應該DFSb所在的連通塊。在DFS的過程中,每搜到一個點,我們就計算出從1到這個點的路徑的異或和,消元後看一下跟以前的是否重復,如果不重復就扔到set裏。此外,每搜到一條樹邊,我們就繼續向下搜索;每搜到一條非樹邊,我們就又得到了一個簡單環,按照1中的處理方法去處理就行了。
3.若a,b都沒有被訪問過(我們也不知道它將會是樹邊還是非樹邊),不用管就好。
感覺實現要比理論簡單一些。
#include <cstdio> #include <cstring> #include <iostream> #include <set> #include <algorithm> typedef long long ll; using namespace std; int n,m,cnt,q; int to[40010],next[40010],head[10010],pa[40010],pb[40010],del[40010],tag[40010],vis[10010]; ll val[40010],dis[10010],pc[40010],ans[40010]; ll v[110]; set<ll> s; set<ll>::iterator it; bool cmp(ll a,ll b) { return a>b; } ll query(ll x) { for(int j=1;j<=v[0];j++) if((x^v[j])<x) x^=v[j]; return x; } void updata(ll x) { if(!x) return ; ll tmp; for(it=s.begin();it!=s.end();it=s.upper_bound(tmp)) { tmp=*it; if((tmp^x)<tmp) s.erase(it),s.insert(tmp^x); } v[++v[0]]=x; for(int j=v[0];j>=2;j--) { if(v[j]>v[j-1]) swap(v[j],v[j-1]); else break; } } void add(int a,int b,ll c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } void dfs(int x,int fa) { vis[x]=1; ll tmp=query(dis[x]); if(tmp&&s.find(tmp)==s.end()) s.insert(tmp); for(int i=head[x];i!=-1;i=next[i]) { if(to[i]==fa) continue; if(!vis[to[i]]) dis[to[i]]=dis[x]^val[i],dfs(to[i],x); else updata(query(dis[x]^dis[to[i]]^val[i])); } } void insert(int a,int b,ll c) { add(a,b,c),add(b,a,c); if(vis[a]&&vis[b]) { updata(query(dis[a]^dis[b]^c)); return ; } if(!vis[a]&&!vis[b]) return ; if(vis[b]) swap(a,b); dis[b]=dis[a]^c,dfs(b,a); } int main() { memset(head,-1,sizeof(head)); vis[1]=1; scanf("%d%d%d",&n,&m,&q); int i; for(i=1;i<=m;i++) scanf("%d%d%lld",&pa[i],&pb[i],&pc[i]); for(i=1;i<=q;i++) scanf("%d",&del[i]),tag[del[i]]=1; for(i=1;i<=m;i++) if(!tag[i]) insert(pa[i],pb[i],pc[i]); s.insert(0); ans[q+1]=(s.size()*1ll<<v[0])-1; for(i=q;i>=1;i--) { insert(pa[del[i]],pb[del[i]],pc[del[i]]); ans[i]=(s.size()*(1ll<<v[0]))-1; } for(i=1;i<=q+1;i++) printf("%lld\n",ans[i]); return 0; }
【BZOJ2322】[BeiJing2011]夢想封印 高斯消元求線性基+DFS+set