[dfs][前缀和][二分] Jzoj P5919 逛公园
题解
- 首先,我们先打一个tarjan来求环,然后我们发现,对于一个环只用知道环内结点编号的最大值和最小值就知道是否被包含了
- 这样的话,题目就转换为了在一个数轴上有一些线段,每次询问一个区间不包含线段的方案数
- 设k[i]为i能延伸的最远处,这个数组怎么求呢,那么就是在对于每条线段[l,r],我们令k[r]=l+1,然后从左到右k[1]=1,l[i]=max(k[i],k[i−1])(i>2)
- 还可以打一个前缀和sum[i],为前i个最多能延伸的长度
- 这样我们对于一组询问就可以统计答案了
- 若当前询问区间为[x,y],假设其中的一个位置i,若满足k[i]>=x,那么它对答案的贡献就是i−k[i]+1
- 若满足k[i]<x,那么对答案的贡献就是i−x+1
- 这样的话,我们就能二分出来一个位置i满足k[i]>=x k[i-1]<x
- 那么[i,y]的区间就可以用前缀和直接算,对于[x,i-1]就是个等差数列,(首项+末项)*项数/2
代码
1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 #define N 600010 5 using namespace std; 6 struct edge{int to,from;}e[N]; 7 int n,m,cnt,Q,fa[N],dep[N],k[N],head[N]; 8 bool vis[N]; 9 long long sum[N]; 10 void insert(int x,int y) { e[++cnt].to=y; e[cnt].from=head[x]; head[x]=cnt; } 11 void dfs(int x) 12 { 13 vis[x]=1; 14 for (int i=head[x];i;i=e[i].from) 15 if (e[i].to!=fa[x]) 16 { 17 if (vis[e[i].to]) 18 { 19 if (dep[e[i].to]>dep[x]) continue; 20 int mx=max(x,e[i].to),mn=min(x,e[i].to); 21 for (int j=x;j!=e[i].to;j=fa[j]) mx=max(mx,j),mn=min(mn,j); 22 k[mx]=mn; 23 continue; 24 } 25 dep[e[i].to]=dep[x]+1,fa[e[i].to]=x,dfs(e[i].to); 26 } 27 } 28 int main() 29 { 30 scanf("%d%d",&n,&m); 31 for (int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),insert(x,y),insert(y,x); 32 dfs(1); 33 for (int i=1;i<=n;i++) k[i]=max(k[i-1],k[i]),sum[i]=sum[i-1]+(i-k[i]); 34 scanf("%d",&Q); 35 while (Q--) 36 { 37 int x,y; 38 scanf("%d%d",&x,&y); 39 long long l=y+1; 40 for (int j=1<<18;j;j>>=1) if (l-j>0&&k[l-j]>=x) l-=j; 41 printf("%lld\n",(sum[y]-sum[l-1]+(long long)(l-x)*(l-x+1)/2)); 42 } 43 }