2014徐寅展論文《線段樹在一類分治問題上的應用》讀后感。
線段樹分治
線段樹分治其實就是有撤銷操作的時間分治。
題目讓你維護一些信息,每次可以詢問,可以執行一種操作,也可以將之前的某個這種操作撤回。
操作容易維護,但撤回操作不容易維護。
需要將操作,詢問都離線下來。將時間軸畫出來,那么每個操作只在時間軸上的一個區間內生效。
用線段樹給這個區間打上這個操作的標記,維護信息。
TJOI2018 數學計算
小豆現在有一個數x,初始值為1. 小豆有Q次操作,操作有兩種類型:
- m: x = x * m ,輸出 x%mod;
- pos: x = x / 第pos次操作所乘的數(保證第pos次操作一定為類型1,對於每一個類型1 的操作至多會被除一次),輸出x%mod
Q ≤ 100000, mod ≤ 1000000000
題解
線段樹分治的做法非常顯然。此題要維護的東西過於簡單,以至於它根本就不能反映出線段樹分治的精髓。
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int N=100000+10;
int q,M,a[N],s[N<<2];
#define lc (x<<1)
#define rc (x<<1|1)
void build(int x,int l,int r){
s[x]=1;
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
}
void change(int x,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr) return s[x]=(LL)s[x]*v%M,void();
int mid=(l+r)>>1;
if(ql<=mid) change(lc,l,mid,ql,qr,v);
if(qr>mid) change(rc,mid+1,r,ql,qr,v);
}
void query(int x,int l,int r,int prod){
prod=(LL)prod*s[x]%M;
if(l==r) return printf("%d\n",prod),void();
int mid=(l+r)>>1;
query(lc,l,mid,prod),query(rc,mid+1,r,prod);
}
void real_main(){
read(q),read(M);
fill(a+1,a+q+1,-1);
build(1,1,q);
for(int i=1;i<=q;++i){
if(read<int>()==1) a[i]=read<int>();
else{
int pos=read<int>();
change(1,1,q,pos,i-1,a[pos]),a[pos]=-1;
}
}
for(int i=1;i<=q;++i)
if(a[i]!=-1) change(1,1,q,i,q,a[i]);
query(1,1,q,1);
}
int main(){
for(int T=read<int>();T--;) real_main();
return 0;
}
實際上線段樹單點修改就可以了,像可持久化數組那樣。
另外我自己還想出來一種分塊做法。把小於等於 \(\sqrt{M}\) 的質因數用線段樹維護,大於 \(\sqrt{M}\) 直接暴力維護。因為 M 最多只有一個大於根號的質因子,所以這個質因子單獨維護即可。其余大於根號的可以用 EXGCD 求逆元。
BZOJ4311 向量
你要維護一個向量集合,支持以下操作:
- 插入一個向量(x,y)
- 刪除插入的第i個向量
- 查詢當前集合與(x,y)點積的最大值是多少。如果當前是空集輸出0
n<=200000 1<=x,y<=106
題解
給出一堆點 x,y 和詢問 a,b ,求 ax+by 的最大值。
設 ax+by=c ,則整理可得 \(y=−\frac{a}{b}x+\frac{c}{b}\) ,要讓 c 最大即讓截距最大。因此答案一定在上凸殼上取到。
由於有刪除操作,因此使用線段樹對時間分治,把一個向量出現的時間段分為線段樹上的 log 個。
然后考慮怎么統計答案:由於平衡樹維護凸包的時間復雜度時均攤的,因此不能在線段樹上進行插入與恢復的操作。考慮到答案所在的每一段互不影響,因此可以對於線段樹的每個節點維護凸包,查詢時在每一段上進行二分,取最大值即為答案。
因此使用vector,對於線段樹的每個節點,維護上凸殼;查詢時在其到線段樹根節點的每個節點的凸包上二分,每個節點求出最優解后再取最大值即為答案。
這樣做的時間復雜度時 O(n log2 n) 。
存在一種更優的解法:對每個詢問按照 \(−\frac{a}{b}\) 從大到小排序,這樣決策就是按 x 單調不降的了。每個節點維護當前決策位置,統計時不斷判斷下一個是否比當前的優即可。
時間復雜度 O(n log n)。
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
struct Vector {int x,y;};
Vector operator-(co Vector&a,co Vector&b){
return (Vector){a.x-b.x,a.y-b.y};
}
LL dot(co Vector&a,co Vector&b){
return (LL)a.x*b.x+(LL)a.y*b.y;
}
LL cross(co Vector&a,co Vector&b){
return (LL)a.x*b.y-(LL)a.y*b.x;
}
struct node {Vector p;int l,r;};
il bool cmp_x(co node&a,co node&b){ // x,y
return a.p.x!=b.p.x?a.p.x<b.p.x:a.p.y<b.p.y;
}
il bool cmp_s(co node&a,co node&b){ // slope
return cross(a.p,b.p)<0;
}
co int N=200000+10;
node a[N],b[N],c[N];
int ta,tb,tc,val[N];
vector<Vector> v[N<<2];
int pos[N<<2];
LL ans[N];
#define lc (x<<1)
#define rc (x<<1|1)
void insert(int x,int l,int r,int ql,int qr,co Vector&p){
if(ql<=l&&r<=qr){
while(v[x].size()>=2&&cross(p-v[x][v[x].size()-1],p-v[x][v[x].size()-2])<=0) v[x].pop_back();
return v[x].push_back(p);
}
int mid=(l+r)>>1;
if(ql<=mid) insert(lc,l,mid,ql,qr,p);
if(qr>mid) insert(rc,mid+1,r,ql,qr,p);
}
LL query(int x,int l,int r,int k,co Vector&p){
LL ans=0;
if(v[x].size()){
while(pos[x]<(int)v[x].size()-1&&dot(p,v[x][pos[x]+1])>=dot(p,v[x][pos[x]])) ++pos[x];
ans=dot(p,v[x][pos[x]]);
}
if(l==r) return ans;
int mid=(l+r)>>1;
if(k<=mid) return max(ans,query(lc,l,mid,k,p));
else return max(ans,query(rc,mid+1,r,k,p));
}
int main(){
int n=read<int>();
for(int i=1;i<=n;++i){
int opt=read<int>();
if(opt==1) read(a[i].p.x),read(a[i].p.y),a[i].l=i,a[i].r=n,val[++ta]=i;
else if(opt==2) a[val[read<int>()]].r=i-1,a[i].r=-1;
else ++tc,read(c[tc].p.x),read(c[tc].p.y),c[tc].l=i;
}
for(int i=1;i<=n;++i)
if(a[i].l) b[++tb]=a[i];
sort(b+1,b+tb+1,cmp_x);
for(int i=1;i<=tb;++i) insert(1,1,n,b[i].l,b[i].r,b[i].p);
sort(c+1,c+tc+1,cmp_s);
for(int i=1;i<=tc;++i) ans[c[i].l]=query(1,1,n,c[i].l,c[i].p);
for(int i=1;i<=n;++i)
if(!a[i].r) printf("%lld\n",ans[i]);
return 0;
}
這個long long
真的是一處不開見祖宗。
BZOJ4184 shallot
小苗去市場上買了一捆小蔥苗,她突然一時興起,於是她在每顆小蔥苗上寫上一個數字,然后把小蔥叫過來玩游戲。
每個時刻她會給小蔥一顆小蔥苗或者是從小蔥手里拿走一顆小蔥苗,並且讓小蔥從自己手中的小蔥苗里選出一些小蔥苗使得選出的小蔥苗上的數字的異或和最大。
這種小問題對於小蔥來說當然不在話下,但是他的身邊沒有電腦,於是他打電話給同為 OI 選手的你,你能幫幫他嗎?
你只需要輸出最大的異或和即可,若小蔥手中沒有小蔥苗則輸出0。
N<=500000,Ai<=231-1
題解
每個元素都有一段存在時間,所以考慮線段樹分治。
此題求最大異或和顯然要用到線性基。但是由於修改的貢獻不是獨立的,不能分開算取最優解。然而線性基狀態很少,所以線段樹節點里的線性基就可以下傳到葉子。
從別人的代碼來看,給出的數字是不重復的,盡管題面里面沒有說這一點。於是有用unordered_map的簡化寫法。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int N=1048576+10;
tr1::unordered_map<int,int> H;
vector<int> s[N];
#define lc (x<<1)
#define rc (x<<1|1)
void insert(int x,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr) return s[x].push_back(v);
int mid=(l+r)>>1;
if(ql<=mid) insert(lc,l,mid,ql,qr,v);
if(qr>mid) insert(rc,mid+1,r,ql,qr,v);
}
struct node{
int a[31];
void insert(int v){
for(int i=30;i>=0;--i)if(v>>i&1){
if(!a[i]) {a[i]=v;break;}
v^=a[i];
}
}
int query(){
int ans=0;
for(int i=30;i>=0;--i)
if((ans^a[i])>ans) ans^=a[i];
return ans;
}
}empty;
void query(int x,int l,int r,node v){
for(int i=0;i<(int)s[x].size();++i) v.insert(s[x][i]);
if(l==r) {printf("%d\n",v.query());return;}
int mid=(l+r)>>1;
query(lc,l,mid,v),query(rc,mid+1,r,v);
}
int main(){
int n=read<int>();
for(int i=1;i<=n;++i){
int a=read<int>();
if(a<0) insert(1,1,n,H[-a],i-1,-a),H.erase(-a);
else H[a]=i;
}
for(tr1::unordered_map<int,int>::iterator i=H.begin();i!=H.end();++i)
insert(1,1,n,i->second,n,i->first);
query(1,1,n,empty);
return 0;
}
FJOI2015 火星商店問題
火星上的一條商業街里按照商店的編號1,2 ,…,n ,依次排列着n個商店。商店里出售的琳琅滿目的商品中,每種商品都用一個非負整數val來標價。每個商店每天都有可能進一些新商品,其標價可能與已有商品相同。
火星人在這條商業街購物時,通常會逛這條商業街某一段路上的所有商店,譬如說商店編號在區間[L,R]中的商店,從中挑選1件自己最喜歡的商品。每個火星人對商品的喜好標准各不相同。通常每個火星人都有一個自己的喜好密碼x。對每種標價為val的商品,喜好密碼為x的火星人對這種商品的喜好程度與val異或x的值成正比。也就是說,val xor x的值越大,他就越喜歡該商品。每個火星人的購物卡在所有商店中只能購買最近d天內(含當天)進貨的商品。另外,每個商店都有一種特殊商品不受進貨日期限制,每位火星人在任何時刻都可以選擇該特殊商品。每個商店中每種商品都能保證供應,不存在商品缺貨的問題。
對於給定的按時間順序排列的事件,計算每個購物的火星人的在本次購物活動中最喜歡的商品,即輸出val xor x的最大值。這里所說的按時間順序排列的事件是指以下2種事件:
事件0,用三個整數0,s,v,表示編號為s的商店在當日新進一種標價為v 的商品。
事件1,用5個整數1,L,R,x,d,表示一位火星人當日在編號為L到R的商店購買d天內的商品,該火星人的喜好密碼為x。
每天的事件按照先事件0,后事件1的順序排列。
n, m <= 100000。數據中,價格不大於100000
題解
如果詢問中的d固定,這道題可以把限制轉化到修改而不是詢問上。
- 編號為L到R的商店:相當於每個商品有個商店號 id。
- 購買d天內的商品:相當於每個商品有個生效時間 [t,t+d-1]。
我們發現這樣的轉化對特殊商品也是成立的,看來找對了方向。
求異或和最大顯然要用到Trie,商店號的限制可以用可持久化Trie實現。
考慮離線后如何處理。可持久化Trie不好下傳,但是由於修改的貢獻獨立,所以可以對線段樹每個節點建一次可持久化Trie。
把所有的詢問在線段樹對應的葉子到根的路徑上存一遍,這樣建完可持久化Trie后處理詢問即可。
時間復雜度 \(O(n \log^2 n)\)。一個小trick:在外層按照id排序后插入線段樹,內層建可持久化Trie就方便許多。
考慮d不固定怎么做,顯然不能進行轉化,只能硬上了(開始看錯題了,差評)。
當然還有另一種做法,不用轉化商品。對特殊商品直接在外面計算。
把詢問存到線段樹節點中,然后對線段樹每個節點建可持久化Trie。然后遞歸的時候需要像CDQ分治那樣將修改按照時間划分,同時維護id有序。
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int N=100000+10;
int root[N],tot;
namespace Trie{
struct node{int ch[2],siz;}t[N*18];
void insert(int&x,int fa,int v,int p){
t[x=++tot]=t[fa],++t[x].siz;
if(p==-1) return;
bool c=v>>p&1;
insert(t[x].ch[c],t[fa].ch[c],v,p-1);
}
int query(int l,int r,int v,int p){
if(p==-1) return 0;
bool c=v>>p&1;
if(t[t[r].ch[c^1]].siz-t[t[l].ch[c^1]].siz)
return 1<<p|query(t[l].ch[c^1],t[r].ch[c^1],v,p-1);
return query(t[l].ch[c],t[r].ch[c],v,p-1);
}
}
struct buy {int s,t,v;}q[N],tmp1[N],tmp2[N]; // shop,time,val
il bool operator<(co buy&a,co buy&b){
return a.s<b.s;
}
struct ask {int sl,sr,tl,tr,v;}p[N]; // shop limit,time limit,val
int n,m,cnt1,cnt2,ans[N];
vector<int> seg[N<<2];
#define lc (x<<1)
#define rc (x<<1|1)
void modify(int x,int l,int r,int tl,int tr,int id){
if(tl>tr) return;
if(tl<=l&&r<=tr) return seg[x].push_back(id);
int mid=(l+r)>>1;
if(tl<=mid) modify(lc,l,mid,tl,tr,id);
if(tr>mid) modify(rc,mid+1,r,tl,tr,id);
}
int st[N],top;
int binary(int s){
int l=0,r=top; // edit 1:l=0
while(l<r){
int mid=(l+r+1)>>1;
if(st[mid]<=s) l=mid;
else r=mid-1;
}
return l;
}
void calc(int x,int ql,int qr){
top=tot=0;
for(int i=ql;i<=qr;++i){
st[++top]=q[i].s;
Trie::insert(root[top],root[top-1],q[i].v,16); // edit 2:top
}
for(int i=0;i<(int)seg[x].size();++i){
int id=seg[x][i];
int l=binary(p[id].sl-1),r=binary(p[id].sr);
ans[id]=max(ans[id],Trie::query(root[l],root[r],p[id].v,16));
}
}
void divide(int x,int l,int r,int ql,int qr){
if(ql>qr) return;
calc(x,ql,qr);
if(l==r) return;
int mid=(l+r)>>1,t1=0,t2=0;
for(int i=ql;i<=qr;++i){
if(q[i].t<=mid) tmp1[++t1]=q[i];
else tmp2[++t2]=q[i];
}
copy(tmp1+1,tmp1+t1+1,q+ql),copy(tmp2+1,tmp2+t2+1,q+ql+t1);
divide(lc,l,mid,ql,ql+t1-1),divide(rc,mid+1,r,ql+t1,qr);
}
int main(){
read(n),read(m);
for(int i=1;i<=n;++i) Trie::insert(root[i],root[i-1],read<int>(),16);
for(int i=1;i<=m;++i){
if(read<int>()==0){
int s=read<int>(),v=read<int>();
++cnt1,q[cnt1]=(buy){s,cnt1,v};
}
else{
int sl=read<int>(),sr=read<int>(),v=read<int>(),d=read<int>();
ans[++cnt2]=Trie::query(root[sl-1],root[sr],v,16);
p[cnt2]=(ask){sl,sr,max(1,cnt1-d+1),cnt1,v};
}
}
for(int i=1;i<=cnt2;++i) modify(1,1,cnt1,p[i].tl,p[i].tr,i);
sort(q+1,q+cnt1+1),divide(1,1,cnt1,1,cnt1);
for(int i=1;i<=cnt2;++i) printf("%d\n",ans[i]);
return 0;
}
HNOI2010 城市建設
PS國是一個擁有諸多城市的大國,國王Louis為城市的交通建設可謂絞盡腦汁。Louis可以在某些城市之間修建道路,在不同的城市之間修建道路需要不同的花費。Louis希望建造最少的道路使得國內所有的城市連通。但是由於某些因素,城市之間修建道路需要的花費會隨着時間而改變,Louis會不斷得到某道路的修建代價改變的消息,他希望每得到一條消息后能立即知道使城市連通的最小花費總和, Louis決定求助於你來完成這個任務。
對於100%的數據, n≤20000,m≤50000,Q≤50000。
題解
這道題是CDQ分治,不過yyb放了一個線段樹分治+LCT的標簽所以我就把這道題放到規划里了。
題目核心思想是分治以及縮小圖的規模,有兩個操作
Contraction:刪除必須邊
把待修改的邊標記成 −∞,然后跑一次最小生成樹,顯然這時候出現在生成樹中非 −∞ 的邊肯定會在之后的生成樹中,所以我們記錄下它們的權值和,然后在下一層分治的時候刪除這些邊
Reduction:刪除無用邊
把待修改的邊標記成 ∞,然后跑一次最小生成樹,沒出現在生成樹中的非 ∞ 邊在之后的生成樹中肯定也不會出現(因為在當前圖構造出的MST中,加入這條邊會生成一個環,而且它是這個環中權值最大的邊,然而在之后的圖中,∞ 邊只會變小,它還是權值最大的邊,所以肯定不會出現在MST中),於是將它們刪除,繼續分治
然后每層分治過程中都進行 Contraction-Reduction 來縮小圖的規模,在最后一層分治的時候使修改生效,做一遍MST就可以了(這時候圖已經很小了所以會很快)
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int N=20000+10,M=50000+10,inf=1e9;
struct edge {int u,v,w,id;}e[16][M],d[M],t[M];
il bool operator<(co edge&a,co edge&b){
return a.w<b.w;
}
struct ask {int x,y;}q[M];
int fa[M],siz[M],mp[M],wt[M];
LL ans[M];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
il void merge(int x,int y){
x=find(x),y=find(y);
if(siz[x]>siz[y]) swap(x,y);
siz[y]+=siz[x],fa[x]=y;
}
void reset(int n,co edge*e){ // reset disjoint set
for(int i=1;i<=n;++i)
fa[e[i].u]=e[i].u,fa[e[i].v]=e[i].v,siz[e[i].u]=siz[e[i].v]=1;
}
LL contract(int&n){
int tmp=0;
sort(d+1,d+n+1);
for(int i=1;i<=n;++i)
if(find(d[i].u)!=find(d[i].v))
merge(d[i].u,d[i].v),t[++tmp]=d[i];
reset(tmp,t);
LL ans=0;
for(int i=1;i<=tmp;++i) // add essential edge
if(t[i].w!=-inf and find(t[i].u)!=find(t[i].v))
merge(t[i].u,t[i].v),ans+=t[i].w;
tmp=0;
for(int i=1;i<=n;++i)
if(find(d[i].u)!=find(d[i].v))
t[++tmp]=(edge){fa[d[i].u],fa[d[i].v],d[i].w,d[i].id},mp[d[i].id]=tmp;
reset(n,d);
n=tmp,copy(t+1,t+tmp+1,d+1);
return ans;
}
void reduce(int&n){
int tmp=0;
sort(d+1,d+n+1);
for(int i=1;i<=n;++i){ // remove useless edge
if(find(d[i].u)!=find(d[i].v))
merge(d[i].u,d[i].v),t[++tmp]=d[i],mp[d[i].id]=tmp;
else if(d[i].w==inf)
t[++tmp]=d[i],mp[d[i].id]=tmp;
}
reset(n,d);
n=tmp,copy(t+1,t+tmp+1,d+1);
}
void divide(int now,int n,int l,int r,LL sum){
if(l==r) wt[q[l].x]=q[l].y;
for(int i=1;i<=n;++i){
e[now][i].w=wt[e[now][i].id];
d[i]=e[now][i],mp[d[i].id]=i;
}
if(l==r){
ans[l]=sum;
sort(d+1,d+n+1);
for(int i=1;i<=n;++i)
if(find(d[i].u)!=find(d[i].v))
merge(d[i].u,d[i].v),ans[l]+=d[i].w;
return reset(n,d);
}
for(int i=l;i<=r;++i) d[mp[q[i].x]].w=-inf;
sum+=contract(n);
for(int i=l;i<=r;++i) d[mp[q[i].x]].w=inf;
reduce(n);
copy(d+1,d+n+1,e[now+1]+1);
int mid=(l+r)>>1;
divide(now+1,n,l,mid,sum),divide(now+1,n,mid+1,r,sum);
}
int main(){
int n=read<int>(),m=read<int>(),q=read<int>();
for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1;
for(int i=1;i<=m;++i){
int u=read<int>(),v=read<int>(),w=read<int>();
e[0][i]=(edge){u,v,w,i},wt[i]=w;
}
for(int i=1;i<=q;++i)
read(::q[i].x),read(::q[i].y);
divide(0,m,1,q,0);
for(int i=1;i<=q;++i) printf("%lld\n",ans[i]);
return 0;
}
BZOJ4644 經典傻逼題
考慮一張N個點的帶權無向圖,點的編號為1到N。 對於圖中的任意一個點集(可以為空或者全集),所有恰好有一個端點在這個點集中的邊組成的集合被稱為割。 一個割的權值被定義為所有在這個割上的邊的異或和。
一開始這張圖是空圖, 現在,考慮給這張無向圖不斷的加邊, 加入每條邊之后,你都要求出當前權值最大的割的權值, 注意加入的邊永遠都不會消失。
1 ≤ N≤ 500, 1 ≤ M ≤ 1000, 0 ≤ L < 1000, 1 ≤ x,y≤ N
題解
首先很容易發現,我們把每個點的權值設成所有它在的邊的權值異或和,原題轉換為求一個最大異或和的點集,最大異或和我們考慮用線性基求出。
但是線性基不支持刪除操作,我們采用線段樹分治避免掉刪除操作。
這破題權值竟然要開bitset才能存……不過看別人的代碼讓我知道了怎么撤銷修改。由於線性基每次的修改只有賦值操作,所以記錄一下更改了哪些位置,撤銷的時候講這些位置變成0就好了。
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int N=500+1,M=1000+1,L=1000;
int last[N];
typedef bitset<L> basis;
struct node{
vector<basis> s;
vector<int> p;
}tree[M<<2];
basis bas[L],now;
#define lc (x<<1)
#define rc (x<<1|1)
void insert(int x,int l,int r,int ql,int qr,co basis&v){
if(ql<=l&&r<=qr)
return tree[x].s.push_back(v);
int mid=(l+r)>>1;
if(ql<=mid) insert(lc,l,mid,ql,qr,v);
if(qr>mid) insert(rc,mid+1,r,ql,qr,v);
}
void query(int x,int l,int r){
for(int i=0;i<(int)tree[x].s.size();++i)
for(int j=L-1;j>=0;--j)if(tree[x].s[i][j]){
if(!bas[j][j]) {
bas[j]=tree[x].s[i],tree[x].p.push_back(j);
break;
}
tree[x].s[i]^=bas[j];
}
if(l==r){
now.reset();
for(int i=L-1;i>=0;--i)
if(!now[i] and bas[i][i]) now^=bas[i];
int l=L-1;
while(l and !now[l]) --l;
for(;l>=0;--l) putchar(now[l]+'0');
puts("");
}
else{
int mid=(l+r)>>1;
query(lc,l,mid),query(rc,mid+1,r);
}
for(int i=0;i<(int)tree[x].p.size();++i)
bas[tree[x].p[i]].reset();
}
int main(){
read<int>();
int n=read<int>(),m=read<int>();
for(int i=1;i<=m;++i){
int u=read<int>(),v=read<int>();
static char str[L+1];
scanf("%s",str);
if(u==v) continue;
now.reset();
int len=strlen(str);
for(int j=0;j<len;++j) now[len-j-1]=str[j]-'0';
// cerr<<"w=";
// for(int j=len-1;j>=0;--j) cerr<<char(now[j]+'0');
// cerr<<endl;
if(last[u]) insert(1,1,m,last[u],i-1,bas[u]);
bas[u]^=now,last[u]=i;
if(last[v]) insert(1,1,m,last[v],i-1,bas[v]);
bas[v]^=now,last[v]=i;
}
for(int i=1;i<=n;++i){
if(last[i] and last[i]<=m) insert(1,1,m,last[i],m,bas[i]);
bas[i].reset();
}
query(1,1,m);
return 0;
}