临时跑去D班取听神仙课,不会的还是好多(我真菜);
1.tarjan算法;
tarjan求强连通分量,进行缩点,求缩点后出度为0的点,如果存在两个及以上的点出度都为零 那么输出无解;
否则输出这个强联通分量的大小;

#include<bits/stdc++.h> #define N 100500 using namespace std; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } struct gg { int next,y; }e[N<<1]; int lin[N],dfn[N],low[N],cnt,tot,hl,n,m; int du[N],id[N],all[N]; bool v[N]; stack<int>s; inline void add(int x,int y) { cnt++; e[cnt].y=y; e[cnt].next=lin[x]; lin[x]=cnt; } void tarjan(int x) { dfn[x]=low[x]=++tot; s.push(x);v[x]=true; for(int i=lin[x];i;i=e[i].next) { int u=e[i].y; if(!dfn[u]) { tarjan(u); low[x]=min(low[x],low[u]); } else if(v[u])low[x]=min(low[x],dfn[u]); } int k; if(low[x]==dfn[x]) { ++hl; do { k=s.top();s.pop(); v[k]=false; id[k]=hl;all[hl]++; }while(x!=k); } } int main() { read(n);read(m); int a,b; for(int i=1;i<=m;i++) { read(a);read(b); add(a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int k=1;k<=n;k++) { for(int i=lin[k];i;i=e[i].next) { int u=e[i].y; if(id[k]!=id[u]) du[id[k]]++; } } int t=0; for(int i=1;i<=hl;i++) if(!du[i]) { if(t) { puts("0"); return 0; } t=i; } printf("%d ",all[t]); return 0; }
2.杀人游戏
tarjan求强连通分量,对于一个强联通的分量,知道其中一个人就知道这个块里的其他人;
细节:考虑一种情况,求完tarjan后缩点,存在一个入度为0的点,它的强联通分量大小为1,他所连的点的入度>1,我们是不需要知道这个点的;
因为他一定能被其他n-1个点确定后推出;如果等于1,那么我们是必须知道这个入度为0的点的身份的;

#include<bits/stdc++.h> #define N 500500 using namespace std; template<typename T>inline void read(T &x) { x=0; register int f=1; register char ch=getchar(); while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } struct pink { int x,y; }a[N<<1]; struct gg { int next,y; }e[N<<1]; int lin[N<<1],dfn[N],low[N],cnt,tot,hl,n,m,ans[N]; int in[N],id[N],all[N],flag; bool v[N]; stack<int>s; inline void add(int x,int y) { cnt++; e[cnt].y=y; e[cnt].next=lin[x]; lin[x]=cnt; } void tarjan(int x) { dfn[x]=low[x]=++tot; s.push(x);v[x]=true; for(int i=lin[x];i;i=e[i].next) { int u=e[i].y; if(!dfn[u]) { tarjan(u); low[x]=min(low[x],low[u]); } else if(v[u]) low[x]=min(low[x],dfn[u]); } int k; if(low[x]==dfn[x]) { ++hl; do { k=s.top();s.pop(); v[k]=false; id[k]=hl;all[hl]++; }while(x!=k); } } int main() { memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); read(n);read(m); for(int i=1;i<=m;i++) { read(a[i].x);read(a[i].y); add(a[i].x,a[i].y); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); memset(lin,0,sizeof(lin)); cnt=0; for(int i=1;i<=m;i++) { if(id[a[i].x]!=id[a[i].y]) { in[id[a[i].y]]++; add(id[a[i].x],id[a[i].y]); } } int ans=0; for(int i=1;i<=hl;i++) { if(!flag&&!in[i]&&all[i]==1) { int p=0; for(int j=lin[i];j;j=e[j].next) { int yy=e[j].y; if(in[yy]==1) p=1; } if(!p) flag=1; } if(!in[i]) ans++; } if(flag) ans--; printf("%.6f ",1.0-(double)ans/(double)n); return 0; }
3.矿场搭建
我们需要tarjan跑出所有的割点,然后dfs每一个联通块及大小,如果这个联通块内部不存在割点,那么我么至少建两个救援所,为了避免其中一个救援所炸掉,
而且我们要明白,救援所是不能建在隔点上的,如果这个联通块中存在两个及以上割点,我们是不需要键救援所的;如果存在一个割点,建两个,设在不是割点的电上;

#include<bits/stdc++.h> using namespace std; const int maxn=510; struct gg { int y,next; }a[maxn*maxn]; template<typename T>inline void read(T &x) { x=0; T f=1,ch=getchar(); while (!isdigit(ch)) ch=getchar(); if (ch=='-') f=-1, ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar(); x*=f; } long long hl,ans=1; int lin[maxn],len; inline void add(int x,int y) { a[++len].y=y; a[len].next=lin[x]; lin[x]=len; } int dfn[maxn],low[maxn],num,root; bool cut[maxn]; inline void tarjan(int x,int fa) { dfn[x]=low[x]=++num; int flag=0; for (int i=lin[x];i;i=a[i].next) { int y=a[i].y; if (!dfn[y]) { tarjan(y,x); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]) { ++flag; if (x!=root||flag>1) cut[x]=1; } } else if (y!=fa) low[x]=min(low[x],dfn[y]); } } int vis[maxn],k,cnt; inline void dfs(int x) { vis[x]=k; ++cnt; for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; if(vis[y]!=k&&cut[y]) ++num,vis[y]=k; if(!vis[y]) dfs(y); } } int main() { //freopen("input.in","r",stdin); //freopen("output.out","w",stdout); for (int ca=1;;++ca) { int n=0,m; read(m); if(!m) exit(0); memset(dfn,0,sizeof(dfn)); memset(vis,0,sizeof(vis)); memset(cut,0,sizeof(cut)); memset(lin,0,sizeof(lin)); hl=k=num=len=0; ans=1; for (int i=1;i<=m;++i) { int x,y;read(x);read(y); n=max(n,max(x,y)); add(x,y);add(y,x); } for (int i=1;i<=n;++i) if(!dfn[i]) root=i,tarjan(i,i); for (int i=1;i<=n;++i) if(!vis[i]&&!cut[i]) { ++k,cnt=num=0; dfs(i); if(!num) hl+=2,ans*=cnt*(cnt-1)/2; if(num==1) hl++,ans*=cnt; } printf("Case %d: %lld %lld ",ca,hl,ans); } return 0; }
圆方树,听懂了但没写题;
竞赛图,强联通竞赛图;
支配树,被支配;
4.炸弹
大致思路:线段树优化建边,区间连边,求tarjan强连通分量,然后反向拓扑;
我觉得自己对线段树优化建边理解的不是特别深刻,也是第一次写;
但是他可以将边优化成log级别的,节省很多空间和时间;
题解:
首先应该明确的是这道题一定是用图论知识来做,当一个炸弹i被引爆时,对于能够被当前这颗炸弹i引爆的炸弹j,我们肯定是要建一条i-->j的边,但是由于题目中的边数较多,所以不可能这样建图,那我们可以想到,当一个炸弹被引爆,那么最总一共被引爆的炸弹一定是连续的,所以就有引出区间问题了,那么区间问题我们就可以用线段树来做,当i炸弹能将(ID)【L,R】的炸弹全部引爆时,就建一条pos【i】-->ID的边,那么这样就一定会形成环,因为这颗炸弹处于区间中间,那么久tarjan缩点,然会就是求每个点能够最远到达哪个点,那么要么就dfs(有点慢),有么就top排序,如果是top排序的话,就要反向建图,从最远的点一步一步的推回去,从而解决题目。
总结:充分利用线段树的区间和树形性质,当图论问题转换成区间问题时,我们就可以利用线段树的特性来优化时间和空间。
原文:https://blog.csdn.net/shiyongyang/article/details/77984795

#include<bits/stdc++.h> using namespace std; #define N 1000010 #define LL long long template<typename T>inline void read(T &x) { x=0;T f=1,ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} x*=f; } int n,tot,tc,num,top,cnt,lin[N<<2],linc[N<<2],vis[N<<2],ins[N<<2],c[N<<2],Stack[N<<2],pos[N<<2]; int dfn[N<<2],low[N<<2]; LL X[N],R[N],mn[N<<2],mx[N<<2],vmin[N<<1],vmax[N<<1],rd[N<<1]; struct gg { int x,y,next; }a[N<<2],e[N<<2]; queue<int> q; inline void add(int x,int y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } inline void build(int l,int r,int p) { if(l==r) { pos[l]=p; return ; } int mid=(l+r)>>1; mn[p]=1ll<<62,mx[p]=-1ll<<62; build(l,mid,p<<1); build(mid+1,r,p<<1|1); add(p,p<<1); add(p,p<<1|1); } inline void connect(int b,int e,int x,int l,int r,int y) { if(b<=l&&e>=r) { add(x,y); //cout<<b<<' '<<e<<endl; return ; } int mid=(l+r)>>1; if(b<=mid) connect(b,e,x,l,mid,y<<1); if(e>mid) connect(b,e,x,mid+1,r,y<<1|1); } void tarjan(int x) { dfn[x]=low[x]=++num,ins[x]=1,Stack[++top]=x; for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; //cout<<x<<' '<<y<<endl; if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]); else if(ins[y]) low[x]=min(low[x],dfn[y]); } if(dfn[x]==low[x]) { int t; cnt++,vmin[cnt]=1ll<<62,vmax[cnt]=-1ll<<62; do { t=Stack[top--],ins[t]=0,c[t]=cnt; vmin[cnt]=min(vmin[cnt],mn[t]),vmax[cnt]=max(vmax[cnt],mx[t]); }while(t!=x); } } int main() { read(n); build(1,n,1); for(int i=1;i<=n;i++) { read(X[i]); read(R[i]); mn[pos[i]]=mx[pos[i]]=X[i]; } for(int i=1;i<=n;i++) { connect(lower_bound(X+1,X+n+1,X[i]-R[i])-X,upper_bound(X+1,X+n+1,X[i]+R[i])-X-1,pos[i],1,n,1); //cout<<pos[i]<<endl; } for(int i=1;i<=4*n;i++) { if(!dfn[i]) { tarjan(i); //cout<<i<<endl; } } //cout<<cnt<<endl; int cc=0; for(int x=1;x<=n*4;x++ ) for(int i=lin[x];i;i=a[i].next) if(c[x]!=c[a[i].y]) e[++cc].y=c[x],e[cc].next=linc[c[a[i].y]],linc[c[a[i].y]]=cc,rd[c[x]]++; for(int i=1;i<=cnt;i++) { if(!rd[a[i].y]) q.push(a[i].y); } while(!q.empty()) { int x=q.front();q.pop(); for(int i=linc[x];i;i=e[i].next) { vmin[e[i].y]=min(vmin[e[i].y],vmin[x]),vmax[e[i].y]=max(vmax[e[i].y],vmax[x]),rd[e[i].y]--; if(!rd[e[i].y]) q.push(e[i].y); } } LL ans=0; for(int i = 1 ; i <= n ; i ++ ) ans=(ans+(long long)(upper_bound(X+1,X+n+1,vmax[c[pos[i]]])-lower_bound(X+1,X+n+1,vmin[c[pos[i]]]))*i) % 1000000007; printf("%lld " , ans%1000000007); return 0; }