线段树分治
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;
}