线段树分治

2014徐寅展论文《线段树在一类分治问题上的应用》读后感。

线段树分治

线段树分治其实就是有撤销操作的时间分治

题目让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个这种操作撤回。

操作容易维护,但撤回操作不容易维护。

需要将操作,询问都离线下来。将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效。

用线段树给这个区间打上这个操作的标记,维护信息。

TJOI2018 数学计算

小豆现在有一个数x,初始值为1. 小豆有Q次操作,操作有两种类型:

  1. m: x = x * m ,输出 x%mod;
  2. 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 向量

你要维护一个向量集合,支持以下操作:

  1. 插入一个向量(x,y)
  2. 删除插入的第i个向量
  3. 查询当前集合与(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;
}

原文地址:https://www.cnblogs.com/autoint/p/segment_tree_divide_and_conquer.html

时间: 2024-10-14 07:55:39

线段树分治的相关文章

3237: [Ahoi2013]连通图 线段树分治

题解: 线段树分治裸题 apio t1是这个所以就学习了一下 #include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct re{ int x,y; }a[N*2]; int b[N+20][5],cnt,now,n,m,k; int ls[N*15],rs[N*15],data[N*15],last[N+20]; int ph[N*4],pt[N*4],count2[N+20],f[N]; bool ft[N

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横

不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个城市),保证任意两个城市都可以通过高速公路互达. 国正在筹划“八纵八横”的高铁建设计划,计划要修建一些高速铁路,每条高速铁路两端也都是城市(可能两端是同一个城市),也都有一个非负整数的经济影响因子.国家还计划在“八纵八横”计划建成之后,将“一带一路”扩展为“一带_路一

bzoj 4137 [FJOI2015]火星商店问题——线段树分治+可持久化01trie树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4137 关于可持久化01trie树:https://www.cnblogs.com/LadyLex/p/7281110.html 看了看它的两道例题,就没写. 特殊商品可以直接用可持久化trie做. 其他部分用线段树分治.修改是单点的,询问是区间,原来想的是把询问区间定位后有 mlogn 个,在线段树的每个叶子上贡献一番:结果TLE了,因为若是在叶子处贡献,一个询问就要做 r-l+1 次.

loj#2312. 「HAOI2017」八纵八横(线性基 线段树分治)

题意 题目链接 Sol 线性基+线段树分治板子题.. 调起来有点自闭.. #include<bits/stdc++.h> #define fi first #define se second #define pb push_back #define bit bitset<B + 1> using namespace std; const int MAXN = 501, B = 1001, SS = 4001; inline int read() { char c = getchar

HAOI2017 八纵八横——线段树分治+线性基

题目大意 给定一个图,每次加一些边,或者删掉一些后来加上去的边,定义一个环的价值为环上所有的边的异或和,重复走的边重复算.每次询问这个时刻图中的所有经过1号点的环的最大价值. 思路 首先考虑对于一个静态的图如何求解图中所有经过1号点的环的最大价值,发现这个经过1号点就是唬人的,图中任意一个环都可以经过1号点再走回来. 于是题目变成了求解图中环的最大价值,可以将图中所有的简单环给拎出来放到线性基里面求最大价值,不难发现这是对的. 然后题目转化为了如何求图中所有的简单环,一般我们可以直接对图dfs找

bzoj4311向量(线段树分治+斜率优化)

第二道线段树分治. 首先设当前向量是(x,y),剩余有两个不同的向量(u1,v1)(u2,v2),假设u1>u2,则移项可得,若(u1,v1)优于(u2,v2),则-x/y>(v1-v2)/(u1-u2),然后维护上凸壳后进行三分即可,复杂度O(nlog2n),如果将询问排序扫一遍,可以优化到O(nlogn),当然我没写. #include<bits/stdc++.h> #define lson l,mid,rt<<1 #define rson mid+1,r,rt&l

线段树分治总结

目录 类型一 例题1:八纵八横 代码: 例题2:时空旅行 首先,要求可以离线. 线段树分治有两种. 类型一 操作基于区间,单点询问. 有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难. 因为,通常情况下,需要实现的逆操作都是很久以前执行的. 但是,如果只撤销上次操作,就会简单得多. 比如,维护一些连通性,或直径,线性基等问题. 这类问题加边很好做,但删边很难实现. 我们可以扫一遍操作,得到每个操作的有效区间. 然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处