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\) 升序排序,否则降序。可以优化一倍的常数。
绝对的正解:可持久化线段树、树状数组
板,桶+莫队。
#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}\) 级别的所以叫根号分治。
例题
首先是查询操作:
对于 \(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;
}
练习题
7.20 分块入门
分块
分块是一种暴力数据结构,相比于线段树等 \(\log n\) 级别的数据结构,分块的时间复杂度是 \(\sqrt{n}\),但码量和通用性更高。一般在想不出正解时来打暴力拿分(说了半天就是高级暴力)。
分块算法把一个序列分成 \(\sqrt{n}\) 个小块,每块长 \(\sqrt{n}\)。以区间修改为例理解分块,需要把 \([l,r]\) 之间的数全部加 \(c\)。
对于修改到的最左边的块,如果该块会被 \(l\) 分成两半,就直接暴力修改 \(l\) 到该块的右端点。改到的最右边的块同理。对于中间的块,直接暴力给每一个块打上一个区间加的 lasytag,查询的时候加上去就行了。
这就是分块,纯纯暴力好吧。
分块求解动态区间 k 的排名
对于每个块内的元素,我们直接在块内排个序。然后修改最左边的块的时候,依旧暴力修改,改完了就重新排个序,最右边的同理。中间的直接打 Tag,不用排序因为全都加同一个数不会改变相对大小关系。
然后查询的时候对于每个块 lower_bound 一下求个和就没了,左右断点的块还是暴力统计。
块状链表
链表+分块
插入的时候,如果一个块的大小大于 \(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 的第二种做法。
看图吧。。。

拆成三段、每次最多连 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】逛森林

浙公网安备 33010602011771号