OI 学习总结 part2

7.24 二次离线莫队

如果一个题目可以使用莫队,但是转移不是 \(O(1)\) 的(通常为 \(\log n\) 级别),并且 \(n\) 达到了 \(10^5\) 左右,就会 TTT。

这时,如果每次转移对答案的贡献可以进行差分,我们就把这些转移也再次离线一遍,用其它算法一起处理,可以降到 \(O(很低)\),这就是二次离线莫队。

如果求 \(f(x,[l,x−1])\),我们转化为 \(f(x,[l,x−1])=f(x,[1,x−1])−f(x,[1,l−1])\)。

这样转化为了一个数对一个前缀的贡献。保存下来所有这样的询问,从左到右扫描数组计算就可以了。

但是这样空间不好,于是我们把贡献分为两类:

1.左边的贡献是一个前缀和它后面一个数的贡献。这可以预处理出来。

2.右边的贡献对于一次移动中所有的 \(x\) 来说不变。打标记的时候,可以只标记左右端点。

于是空间复杂度降为 \(O(n)\)

例题:洛谷 P4887 【模板】莫队二次离线 / 第十四分块(前体)

此题中,我们可以开一个桶,记录 \(i\) 异或前缀的答案有多少个,对于每个 \(a_i\),把所有 popcount 为 \(k\) 的数拿个桶记录一下就可以了。

const int N=1e5+5;
int blo[N],a[N];
struct Q{
	int l,r,id;
	ll res;
}q[N];
inline bool cmp(const Q &a,const Q &b){
	if(blo[a.l]!=blo[b.l]) return a.l<b.l;
	return a.r<b.r;
}
vector<tuple<int,int,int>> v[N];
int t[N],pre[N];
ll res[N];
int main(){
	int n=re,m=re,k=re;
	if(k>14){
		wef(i,1,m) write(0,'\n');
		return 0;
	}
	wef(i,1,n) a[i]=re;
	wef(i,1,m) q[i].l=re,q[i].r=re,q[i].id=i;
	vector<int> buc;
	wef(i,0,(1<<14)-1) if(__builtin_popcount(i)==k) buc.push_back(i);
	int B=sqrt(n);
	wef(i,1,n) blo[i]=(i-1)/B+1;
	sort(q+1,q+1+m,cmp);
	wef(i,1,n){
		for(auto x:buc) ++t[a[i]^x];
		pre[i]=t[a[i+1]];
	}
	memset(t,0,sizeof t);
	int L=1,R=0;
	wef(i,1,m){
		int l=q[i].l,r=q[i].r;
		if(L<l) v[R].emplace_back(L,l-1,-i);
		while(L<l) q[i].res+=pre[L-1],L++;
		if(L>l) v[R].emplace_back(l,L-1,i);
		while(L>l) q[i].res-=pre[L-2],L--;
		if(R<r) v[L-1].emplace_back(R+1,r,-i);
		while(R<r) q[i].res+=pre[R],R++;
		if(R>r) v[L-1].emplace_back(r+1,R,i);
		while(R>r) q[i].res-=pre[R-1],R--;
	}
	int id=0,l=0,r=0;
	wef(i,1,n){
		for(auto x:buc) ++t[a[i]^x];
		for(auto x:v[i]){
			tie(l,r,id)=x;
			int tmp=0;
			wef(j,l,r){
				tmp=t[a[j]];
				if(j<=i&&k==0) tmp--;
				if(id<0) q[-id].res-=tmp;
				else q[id].res+=tmp;
			}
		}
	}
	wef(i,1,m) q[i].res+=q[i-1].res;
	wef(i,1,m) res[q[i].id]=q[i].res;
	wef(i,1,m) write(res[i],'\n');
	return 0;
}

7.23 回滚莫队、树上莫队

回滚莫队

应用场景

如果有些题遇到很难删除但插入很简单,或者很难插入但删除很简单,并且不强制在线,就可以考虑回滚莫队。

算法

回滚莫队的核心思想就是:既然只能实现一个操作,那么就只使用一个操作,剩下的交给回滚解决.

例题:P14420 [JOISC 2014] 历史的研究 / Historical Research

这个题的经典程度(板度)甚至比【模板】回滚莫队 还板!

首先增加答案是很好做的,但是删除不好做,因为很难确定删掉之后最重要的是哪个(就像众数一样)。所以用回滚莫队。

回滚莫队跟莫队一样要先分块然后排序,然后对于每个询问:如果询问左端点所属块 \(B\) 和上一个询问左端点所属块的不同,那么将莫队区间的左端点初始化为 \(B\) 的右端点加 \(1\), 将莫队区间的右端点初始化为 \(B\) 的右端点。

然后如果在一个块里,直接暴力回答。否则不断扩展左右端点直到到达询问的区间,再回答。

然后撤销操作,使莫队区间的左端点回滚到 \(B\) 的右端点加 \(1\)。

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=1e5+5;
struct node{
	int l,r,id;
	node(int _l=0,int _r=0,int _i=0):l(_l),r(_r),id(_i){}
}q[N];
int n,m,B;
int blk[N],st[N],ed[N],b[N],a[N],t[N];
ll res[N],nres;
inline bool cmp(const node &a,const node &b){
	if(blk[a.l]!=blk[b.l]) return blk[a.l]<blk[b.l];
	return a.r<b.r;
}
inline void add(int p){
	t[a[p]]++;
	nres=max(nres,(ll)b[a[p]]*t[a[p]]);
}
inline void del(int p){
	t[a[p]]--;
}
int main(){
	n=re,m=re;
	B=n/sqrt(m);
	wef(i,1,n){
		b[i]=a[i]=re;
		blk[i]=(i+B-1)/B;
		if(!st[blk[i]]) st[blk[i]]=i;
		ed[blk[i]]=i;
	}
	sort(b+1,b+1+n);
	int nn=unique(b+1,b+1+n)-b-1;
	wef(i,1,n) a[i]=lower_bound(b+1,b+1+nn,a[i])-b;
	wef(i,1,m) q[i].l=re,q[i].r=re,q[i].id=i;
	sort(q+1,q+1+m,cmp);
	int L=1,R=0;
	wef(i,1,m){
		if(blk[q[i].l]!=blk[q[i-1].l]){
			wef(j,L,R) del(j);
			nres=0;
			L=ed[blk[q[i].l]]+1;
			R=L-1;
		}
		if(blk[q[i].l]==blk[q[i].r]){
			wef(j,q[i].l,q[i].r) add(j);
			res[q[i].id]=nres;
			wef(j,q[i].l,q[i].r) del(j);
			nres=0;
		}
		else{
			while(R<q[i].r) add(++R);
			ll tmp=nres,bbb=L;
			while(L>q[i].l) add(--L);
			res[q[i].id]=nres;
			while(L<bbb) del(L++);
			nres=tmp;
		}
	} 
	wef(i,1,m) write(res[i],'\n');
	return 0;
}

树上莫队

首先对整颗树求一个欧拉序。

然后分两种情况,第一种:\(u\) 是 \(v\) 的祖先:询问转化为 \(st_u,st_v\)。

否则,询问转化为 \(ed_u,st_v\)。

没啦!树上的询问没我们转化为了序列上的,然后正常跑莫队即可。注意判断没更新到的 LCA。

int N, Q;
int belong[MAXN], block;
struct Query {
    int l, r, ID, lca, ans;
    bool operator < (const Query &rhs) const{
        return belong[l] == belong[rhs.l] ? r < rhs.r : belong[l] < belong[rhs.l];
    //    return belong[l] < belong[rhs.l];
    }
}q[MAXN];
vector<int>v[MAXN];
int a[MAXN], date[MAXN];
void Discretization() {
    sort(date + 1, date + N + 1);
    int num = unique(date + 1, date + N + 1) - date - 1;
    for(int i = 1; i <= N; i++) a[i] = lower_bound(date + 1, date + num + 1, a[i]) - date;    
}
int deep[MAXN], top[MAXN], fa[MAXN], siz[MAXN], son[MAXN], st[MAXN], ed[MAXN], pot[MAXN], tot;
void dfs1(int x, int _fa) {
    fa[x] = _fa; siz[x] = 1;
    st[x] = ++ tot; pot[tot] = x; 
    for(int i = 0; i < v[x].size(); i++) {
        int to = v[x][i];
        if(deep[to]) continue;
        deep[to] = deep[x] + 1;
        dfs1(to, x);
        siz[x] += siz[to];
        if(siz[to] > siz[son[x]]) son[x] = to;
    }
    ed[x] = ++tot; pot[tot] = x;
}
void dfs2(int x, int topfa) {
    top[x] = topfa;
    if(!son[x]) return ;
    dfs2(son[x], topfa);
    for(int i = 0; i < v[x].size(); i++) {
        int to = v[x][i];
        if(top[to]) continue;
            dfs2(to, to);
    }
}
int GetLca(int x, int y) {
    while(top[x] != top[y]) {
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    return deep[x] < deep[y] ? x : y;
}
void DealAsk() {
    for(int i = 1; i <= Q; i++) {
        int x = read(), y = read();
        if(st[x] > st[y]) swap(x, y);
        int _lca = GetLca(x, y);
        q[i].ID = i;
        if(_lca == x) q[i].l = st[x], q[i]. r = st[y];
        else q[i].l = ed[x], q[i].r = st[y], q[i].lca = _lca;
    }
}
int Ans, out[MAXN], used[MAXN], happen[MAXN];
void add(int x) {
    if(++happen[x] == 1) Ans++;
}
void delet(int x) {
    if(--happen[x] == 0) Ans--;
}
void Add(int x) {
    used[x] ? delet(a[x]) : add(a[x]); used[x] ^= 1;
}
void Mo() {
    sort(q + 1, q + Q + 1);
    int l = 1, r = 0, fuck = 0;
    for(int i = 1; i <= Q; i++) {
        while(l < q[i].l) Add(pot[l]), l++, fuck++;
        while(l > q[i].l) l--, Add(pot[l]), fuck++;
        while(r < q[i].r) r++, Add(pot[r]), fuck++;
        while(r > q[i].r) Add(pot[r]), r--, fuck++;
        if(q[i].lca) Add(q[i].lca);
        q[i].ans = Ans;
        if(q[i].lca) Add(q[i].lca);
    }
    for(int i = 1; i <= Q; i++) out[q[i].ID] = q[i].ans;
    for(int i = 1; i <= Q; i++)
        printf("%d\n", out[i]);
}

7.22 莫队、带修莫队

莫队

莫队是对查询进行离线+分块,然后使用双指针维护答案的一种暴力算法。

算法

离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可).

排序方法:

对于区间 \([l,r]\),以 \(l\) 所在块的编号为第一关键字,\(r\) 为第二关键字从小到大排序。

常数优化:如果两个元素所在同一个块中,如果该块的编号为奇数,按 \(r\) 升序排序,否则降序。可以优化一倍的常数。

例题:SPOJ3267 DQUERY - D-query

绝对的正解:可持久化线段树、树状数组

板,桶+莫队。

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=3e6+5;
ll nres,res[N],c[N];
int a[N],L=1,R,B;
struct query{
	int l,r,id;
}q[N];
bool cmp(const query &a,const query &b){
	if(a.l/B!=b.l/B) return a.l<b.l;
	return (a.l/B&1)?(a.r<b.r):(a.r>b.r);
}
inline void add(int p){
	if(!c[a[p]]) ++nres;
	c[a[p]]++;
}
inline void del(int p){
	c[a[p]]--;
	if(!c[a[p]]) --nres;
}
int main(){
	int n=re;
	wef(i,1,n) a[i]=re;
	B=sqrt(n);
	int m=re;
	wef(i,1,m){
		q[i].l=re,q[i].r=re;
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	wef(i,1,m){
		while(L>q[i].l) add(--L);
		while(R<q[i].r) add(++R);
		while(L<q[i].l) del(L++);
		while(R>q[i].r) del(R--);
		res[q[i].id]=nres; 
	}
	wef(i,1,m) write(res[i],'\n');
	return 0;
}

带修莫队

原来的莫队是按两个关键字排序的,我们引入一个新的时间轴 \(T\)。

那么我们的坐标也可以在时间维上移动,即 \([l,r,\text{time}]\) 多了一维可以移动的方向。

时间复杂度 \(\mathcal{O}(n^{\frac{5}{3}})\),块长取 \(n^{\frac{2}{3}}\) 最优。

证明啥的要用啥均值不等式我也不会,以后再说。

const int N=233333;
const int M=1111111;
int sum,cnt[M],a[N],ans[N],cntq=0,cntr=0,n,m,sz;
struct ques{
	int l,r,t,id;
}qq[N],qr[N];
inline void add(int x){
	sum += !cnt[x]++;
}
inline void del(int x){
	sum -= !--cnt[x];
}
inline void upd(int x,int t){
	if(qq[x].l<=qr[t].l&&qr[t].l<=qq[x].r){
		del(a[qr[t].l]);
		add(qr[t].r);
	}
	swap(a[qr[t].l],qr[t].r);
}
bool cmp(const ques&a,const ques&b){
	if(a.l/sz!=b.l/sz)return a.l<b.l;
	if(a.r/sz!=b.r/sz)return a.r<b.r;
	return a.t<b.t;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	n=read(),m=read();
	sz=pow(n,0.666);
	wef(i,1,n)a[i]=read();
	wef(i,1,m){
		char op[5];
		scanf("%s",op);
		int l=read(),r=read();
		if(op[0]=='Q'){
			++cntq;
			qq[cntq].id=cntq;
			qq[cntq].l=l;
			qq[cntq].r=r;
			qq[cntq].t=cntr;
		}
		else{
			qr[++cntr].l=l;
			qr[cntr].r=r;
		}
	}
	sort(qq+1,qq+cntq+1,cmp);
	int lcur=1,rcur=0,tcur=0;
	wef(i,1,cntq){
		while(lcur>qq[i].l)add(a[--lcur]);
		while(lcur<qq[i].l)del(a[lcur++]);
		while(rcur>qq[i].r)del(a[rcur--]);
		while(rcur<qq[i].r)add(a[++rcur]);
		while(tcur<qq[i].t)upd(i,++tcur);
		while(tcur>qq[i].t)upd(i,tcur--);
		ans[qq[i].id]=sum;
	}
	wef(i,1,cntq){
		write(ans[i]);
		endl;
	}
	return 0;
}

7.21 根号分治

根号系列的一大手子(好像根号算法全是高级暴力)。

思想

对于一个问题,如果有两种算法 \(A,B\),满足:

\(A\) 对于小数据比较快,遇到大数据就开不下或者 TLE 爆。

\(B\) 对于大数据比较快,遇到小数据就 TLE 爆(一般是 \(\mathcal{O}(\frac{1}{n})\) 级别的算法)。

我们就设置一个阈值 \(P\),在数据小于 \(P\) 时使用算法 \(A\),否则使用 \(B\)。这样总时间复杂度就可以被控制。

因为这个 \(P\) 一般是 \(\sqrt{n}\) 级别的所以叫根号分治。

例题

洛谷 P3396 哈希冲突

首先是查询操作:

对于 \(x\le\sqrt{n}\),直接使用与处理好的答案 \(b_{x,p}\),表示模 \(x\) 为 \(p\) 的所有下标所对应的数的和,只需开到 \(\sqrt{n}\)

否则,暴力计算答案,每次跳 \(p\)。

然后是修改:

首先枚举改动会影响到的所有 \(b_{i,j}\),将其加上 \(y-a_x\)。

然后直接在原数组上把 \(a_x\) 改成 \(y\)。

时间复杂度:

查询操作,第一种显然是 \(\mathcal{O}(1)\),第二种最大取 \(x=\sqrt{n}+1\),此时是 \(\mathcal{O}(\sqrt{n})\) 的。

修改操作也显然是 \(\mathcal{O}(\sqrt{n})\) 的。

总时间复杂度就是 \(\mathcal{O}(Q\times\sqrt{n})\)。

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=1.5e5+5,B=405;
int a[N];
int b[B][B];
int main(){
	int n=re,Q=re;
	wef(i,1,n) a[i]=re;
	wef(i,1,n){
		wef(j,1,sqrt(n)){
			b[j][i%j]+=a[i];
		}
	}
	while(Q--){
		char op;
		cin>>op;
		int x=re,y=re;
		if(op=='A'){
			if(x*x<=n) write(b[x][y],'\n');
			else{
				ll res=0;
				for(int i=y;i<=n;i+=x) res+=a[i];
				write(res,'\n');
			}
		}
		else{
			wef(i,1,sqrt(n)) b[i][x%i]+=y-a[x];
			a[x]=y;
		}
	}
	return 0;
}

练习题

CF797E

CF1921F

洛谷 P5309 [Ynoi2011] 初始化

7.20 分块入门

分块

分块是一种暴力数据结构,相比于线段树等 \(\log n\) 级别的数据结构,分块的时间复杂度是 \(\sqrt{n}\),但码量和通用性更高。一般在想不出正解时来打暴力拿分(说了半天就是高级暴力)。

分块算法把一个序列分成 \(\sqrt{n}\) 个小块,每块长 \(\sqrt{n}\)。以区间修改为例理解分块,需要把 \([l,r]\) 之间的数全部加 \(c\)。

对于修改到的最左边的块,如果该块会被 \(l\) 分成两半,就直接暴力修改 \(l\) 到该块的右端点。改到的最右边的块同理。对于中间的块,直接暴力给每一个块打上一个区间加的 lasytag,查询的时候加上去就行了。

这就是分块,纯纯暴力好吧。

模板:洛谷 P13976 数列分块入门 1

AC code

分块求解动态区间 k 的排名

模板:洛谷 P13977 数列分块入门 2

对于每个块内的元素,我们直接在块内排个序。然后修改最左边的块的时候,依旧暴力修改,改完了就重新排个序,最右边的同理。中间的直接打 Tag,不用排序因为全都加同一个数不会改变相对大小关系。

然后查询的时候对于每个块 lower_bound 一下求个和就没了,左右断点的块还是暴力统计。

AC code

块状链表

链表+分块

插入的时候,如果一个块的大小大于 \(2\sqrt{n}\),直接把这个块拆成两个。

7.16~7.17 一些建图优化

线段树优化建图

应用场景

如果一个题需要对所有 \(l,r\) 连边 (\(l\in[a,b],r\in[c,d]\),此时 \(a\) 可以等于 \(b\),\(c\) 可以等于 \(d\),即单点),就可以用线段树优化建图,把需要建的边数从 \(n^2\) 级别降至 \(n\log n\) 级别。

方法

建两棵树,一颗由父亲节点向子节点连边,一颗反之。我们分别称之为入树、出树。

对于单点连区间,我们把区间拆成线段树上的 \(\log n\) 级别个节点。然后从单点对应的出树的叶子结点,连向入树的 \(\log n\) 个节点。

对于区间连单点,我们在出树上拆节点,连入树的叶子结点就行了。

对于区间连区间,我们先在出树上拆节点,连到一个新的虚电 \(k\),然后在入树上拆节点,从 \(k\) 连向拆出来的节点就行了。

然后因为入树和出树的叶子结点实际上对应着同一个点,所以要用双向边连接叶子结点。

注意!对于无向图,我们要建两个虚点(就是做两次连有向边的操作),否则就会又自环然后炸掉。

应用

可以直接套最短路、多源最短路、Tarjan 等图论算法。

板子:CodeForces-786B 单点连区间、区间连单点。

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
#define int long long
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=1e5+5,D=3e5;
const ll inf=0x3f3f3f3f3f3f3f3f;
#define ls (p<<1)
#define rs (p<<1|1)
vector<pii> g[N*10];
struct segment_tree{
	int l,r;
}t[N<<2];
int leaf[N<<2];
void build(int p,int l,int r){
	t[p].l=l,t[p].r=r;
	if(l==r){
		leaf[l]=p;
		return;
	}
	int mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	g[p].push_back({ls,0});
	g[p].push_back({rs,0});
	g[ls+D].push_back({p+D,0});
	g[rs+D].push_back({p+D,0});
}
void update(int p,int l,int r,int y,int w,bool op){
	if(l<=t[p].l&&t[p].r<=r){
		if(op) g[p+D].push_back({y,w});
		else g[y].push_back({p,w});
		return;
	}
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid) update(ls,l,r,y,w,op);
	if(r>mid) update(rs,l,r,y,w,op);
}
ll d[N<<3];
void dijkstra(int st){
	memset(d,0x3f,sizeof d);
	d[st]=0;
	priority_queue<pii,vector<pii>,greater<pii>> q;
	q.push({0,st});
	while(!q.empty()){
		pii tt=q.top(); q.pop();
		int x=tt.second;
		ll sum=tt.first;
		if(d[x]<sum) continue;
		for(pii ttt:g[x]){
			int y=ttt.first,w=ttt.second;
			if(d[y]>d[x]+w){
				d[y]=d[x]+w;
				q.push({d[y],y});
			}
		}
	}
}
signed main(){
	int n=re,T=re,st=re;
	build(1,1,n);
	wef(i,1,T){
		int op=re;
		if(op==1){
			int x=re,y=re,w=re;
			g[leaf[x]].push_back({leaf[y],w});
		}
		else{
			int x=re,l=re,r=re,w=re;
			update(1,l,r,leaf[x],w,op%2);
		}
	}
//	wef(i,1,n) write(leaf[i],' ');
	wef(i,1,n) g[leaf[i]].push_back({leaf[i]+D,0}),g[leaf[i]+D].push_back({leaf[i],0});
	dijkstra(leaf[st]+D);
	wef(i,1,n){
		if(d[leaf[i]]==inf) write(-1,' ');
		else write(d[leaf[i]],' ');
	}
	return 0;
}

洛谷-P6348 区间连区间。

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=5e5+5,M=4200010;
struct segment_tree{
	int l,r;
}t[N<<3];
vector<pii> g[M];
int leaf[N];
#define ls (p<<1)
#define rs (p<<1|1)
int n,m,P; 
int D;
void build_in(int p,int l,int r){
	t[p].l=l,t[p].r=r;
    if(l==r)return;
    int mid=l+r>>1;
    build_in(ls,l,mid);
    build_in(rs,mid+1,r);
    g[p].push_back({ls,0});
    g[p].push_back({rs,0});
}
void build_out(int p,int l,int r){
    g[p].push_back({p+n*4,0});
    if(l==r)return (void)(leaf[l]=p+n*4);
    int mid=l+r>>1;
    build_out(ls,l,mid);
    build_out(rs,mid+1,r);
    g[ls+n*4].push_back({p+n*4,0});
    g[rs+n*4].push_back({p+n*4,0});
}
void merge1(int p,int x,int y,int k){
    if(t[p].l>=x&&t[p].r<=y)return (void)(g[k].push_back({p,1}),g[p+n*4].push_back({k+1,1}));
    int mid=t[p].l+t[p].r>>1;
    if(mid>=x)merge1(ls,x,y,k);
    if(mid<y)merge1(rs,x,y,k);
}
void merge2(int p,int x,int y,int k){
    if(t[p].l>=x&&t[p].r<=y) return (void)(g[k+1].push_back({p,1}),g[p+n*4].push_back({k,1}));
    int mid=t[p].l+t[p].r>>1;
    if(mid>=x)merge2(ls,x,y,k);
    if(mid<y)merge2(rs,x,y,k);
}
int d[M];
bool v[M];
void bfs(int st){
	memset(d,0x3f,sizeof d);
	deque<int> q;
	q.push_back(st);
	d[st]=0;
	while(!q.empty()){
		int x=q.front(); q.pop_front();
		for(pii ttt:g[x]){
			int y=ttt.first;
			bool w=ttt.second;
			if(d[y]>d[x]+w){
				d[y]=d[x]+w;
				if(!w) q.push_front(y);
				else q.push_back(y);
			} 
		}
	}
}
int main(){
//	freopen("P6348_2.in","r",stdin);
//	freopen("P6348_2.res","w",stdout);
	n=re,m=re,P=re;
	build_in(1,1,n);
	build_out(1,1,n);
	D=n*4;
//	build(1,1,n);
	wef(i,1,m){
		int a=re,b=re,c=re,d=re;
		merge1(1,a,b,n*8+i*2);
		merge2(1,c,d,n*8+i*2);
	}
	bfs(leaf[P]);
	wef(i,1,n) write(d[leaf[i]]/2,'\n');
	return 0;
}

例题:P3588 [POI 2015 R2] 沙漠 Desert 优化建图+拓扑排序。

P5025 [SNOI2017] 炸弹 优化建图+Tarjan缩点。

CodeForces-1904F 剖+优化建图+拓扑排序。

倍增优化建图

CodeForces-1904F 的第二种做法。

看图吧。。。

image

拆成三段、每次最多连 6 个点,空间时间都很优!

方法

#include<bits/stdc++.h>
#define wef(i,l,r) for(int i=l;i<=r;i++)
#define few(i,r,l) for(int i=r;i>=l;i--)
#define re read()
#define pii pair<int,int>
#define endl putchar('\n')
using ll=long long;
using namespace std;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;}
inline bool write(auto x,char fg=0){bool neg=0;if(x<0){neg=1;putchar('-');}static int sta[40];int top=0;do{sta[top++]=x%10;x/=10;}while(x);if(neg) while(top) putchar('0'-sta[--top]);	else while(top) putchar('0'+sta[--top]);if(fg) putchar(fg);return 0;}
const int N=2e5+5,K=17,NN=N*35+5,MM=1.6e7+5;
int head[NN],to[MM],nxt[MM],in[NN],ec;
inline void addEDGE(int x,int y){
	to[++ec]=y;
	nxt[ec]=head[x];
	head[x]=ec;
	in[y]++;
}
inline IN(int x,int k){
	if(!k) return x;
	return k*N+x;
}
inline int OUT(int x,int k){
	if(!k) return x;
	return (k+17)*N+x;
}
vector<int> yg[N];
int f[N][K+1];
int dep[N],lg2[N];
int n,m;
void dfs(int x,int Fa){
	f[x][0]=Fa;
	dep[x]=dep[Fa]+1;
	for(int y:yg[x]){
		if(y==Fa) continue;
		dfs(y,x);
	}
}
int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	few(i,17,0){
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	}
	if(x==y) return x;
	few(i,17,0){
		if(f[x][i]!=f[y][i]){
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
inline int getSF(int x,int k){
	wef(i,0,17){
		if((k>>i)&1) x=f[x][i];
	}
	return x;
}
inline void addE(int x,int D,int c,int op){
	if(dep[x]<D) return;
	int len=dep[x]-D+1,k=lg2[len],y=getSF(x,len-(1<<k));
	if(op==1){
		addEDGE(c,IN(x,k));
		addEDGE(c,IN(y,k));
	}
	else{
		addEDGE(OUT(x,k),c);
		addEDGE(OUT(y,k),c);
	}
}
void init(){
	for(int j=1;(1<<j)<=n;j++){
		wef(i,1,n){
			f[i][j]=f[f[i][j-1]][j-1];
		}
	}
}
int res[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	n=re,m=re;
	lg2[0]=-1;
	wef(i,1,n) lg2[i]=lg2[i>>1]+1;
	wef(i,1,n-1){
		int x=re,y=re;
		yg[x].push_back(y);
		yg[y].push_back(x);
	}
	dfs(1,0);
	init();
	wef(k,1,17){
		wef(x,1,n){
			addEDGE(IN(x,k),IN(x,k-1));
			addEDGE(OUT(x,k-1),OUT(x,k));
			int p=f[x][k-1];
			if(p){
				addEDGE(IN(x,k),IN(p,k-1));
				addEDGE(OUT(p,k-1),OUT(x,k));
			}
		}
	}
	wef(i,1,m){
		int op=re,a=re,b=re,c=re;
		int lca=LCA(a,b);
		if(c==lca){
			addE(a,dep[c]+1,c,op);
			addE(b,dep[c]+1,c,op);
		}
		else if(LCA(a,c)==c&&dep[c]>=dep[lca]){
			addE(a,dep[c]+1,c,op);
			addE(f[c][0],dep[lca],c,op);
			addE(b,dep[lca],c,op);
		}
		else{
			addE(b,dep[c]+1,c,op);
			addE(f[c][0],dep[lca],c,op);
			addE(a,dep[lca],c,op);
		}
	}
	int tot=n+34*N,ccc=0;
	queue<int> q;
	wef(i,1,tot){
		if(in[i]==0) q.push(i);
	}
	while(!q.empty()){
		int x=q.front(); q.pop();
		if(x>=1&&x<=n) res[x]=++ccc;
		for(int e=head[x];e;e=nxt[e]){
			int y=to[e];
			in[y]--;
			if(!in[y]) q.push(y);
		}
	}
	if(ccc!=n) write(-1);
	else wef(i,1,n) write(res[i],' ');
	return 0;
}

例题:
P5344 【XR-1】逛森林

posted @ 2026-07-21 21:27  vivid/stasis  阅读(23)  评论(0)    收藏  举报