有向图的强连通分量
强联通:两个点之间可以互相到达
如果某个图任意两个点都是强联通的,那么称这个图强联通
如果一个图的子图是强联通的,那么称这个图是强联通子图
一个图的极大强联通子图被称作强连通分量
有强联通分量意味着环
例:受欢迎的牛
如果有环,意味着这个环里的牛都互相喜欢
我们可以先求出环,然后把每一个环都看作一个点,这样整个图就变成了一个DAG(有向无环图)
看有几个点出度为0,如果大于一个点没有出边,就说明没有最受欢迎的牛
如果只有一个,那么强联通分量的大小就是答案
void tarjan(int u){ dfn[u]=++ind; low[u]=dfn[u]; s[top++]=u; in[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(dfn[v]==0){//mei bian li dao, v zai zi shu li mian tarjan(v); low[u]=min(low[u],low[v]); }else{//bian li dao le, v bu zai zi shu li mian if(in[v]){//zai zhan li mian low[u]=min(low[u],dfn[v]); } } } if(dfn[u]==low[u]){//fa xian scc cnt_scc++; while(s[top]!=u){//bu duan chu zhan top--; in[s[top]]=0; scc[s[top]]=cnt_scc; } } }
完整代码:
#include<bits/stdc++.h> using namespace std; int n,m; int cnt,head[50000]; struct edge { int to,nxt; }edg[50005]; inline void add(int from,int to) { edg[++cnt].to=to; edg[cnt].nxt=head[from]; head[from]=cnt; } int dfn[50005],low[50005],ind,in[50005]; int s[50005],top; int cnt_scc; int scc[50005],cntscc[50005]; void tarjan(int x) { dfn[x]=++ind; low[x]=dfn[x]; s[top++]=x; in[x]=1; for(int i=head[x];i;i=edg[i].nxt) { int v=edg[i].to; if(!dfn[v]) { tarjan(v); low[x]=min(low[x],low[v]); } else { if(in[v]) { low[x]=min(low[x],dfn[v]); } } } if(dfn[x]==low[x]) { cnt_scc++; while(s[top]!=x) { top--; in[s[top]]=0; scc[s[top]]=cnt_scc; cntscc[cnt_scc]++; } } } int out[50005]; int ans; int main() { scanf("%d%d",&n,&m); for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y); add(x,y); } for(int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i); } for(int i=1;i<=n;i++) { for(int j=head[i];j;j=edg[j].nxt) { int k=edg[j].to; if(scc[i]!=scc[k]) out[scc[i]]++; } } for(int i=1;i<=cnt_scc;i++) { if(!out[i]) { if(!ans) ans=i; else { cout<<0; return 0; } } } cout<<cntscc[ans]; }
求强联通分量:
首先想到dfs,我们不妨先看看dfs数
发现有向图中dfs树会有横叉边
显然的事实:一个强连通分量一定是dfs树上的连续的一块
我们定义两个数组
dfn[x]表示x是第几个被dfs到的数(时间戳)
low[x]表示当前节点以及他的子树所有出边所能连到的dfn值中最小的一个
如果某一个点的low和他的dfn相同,就意味着出现强联通分量,就把这个强连通分量拿去(没了)
用一个栈来实现,寻找low时只在栈里面找,弹出时不断从栈顶弹出直到弹出这个点
A gift
把g按照升序排序,枚举g0,跑最小生成树(然后就tle)O(m^2logm)
当我们再连一条新边时,会出现环,所以找到环上的最大的边然后删掉它,复杂度O(n)
总复杂度O(mn)
狼抓兔子
平面图的最小割等于对偶图的最短路
最小割:所有割中边权和最小的割
对偶图:边变成点,点变成边 平面图的每一个块变成一个点,每一条边变成垂直的一条边
#include<cstdio> #include<cstring> #define oo 0x3f #define MAXN 2000001 using namespace std; struct edge { int v,to,next; } e[MAXN*2]; int dis[MAXN],q[MAXN],head[MAXN]; bool tag[MAXN]; int n,m,ne,x; void insert(int u,int v,int w) { ne++; e[ne].to=v; e[ne].next=head[u]; e[ne].v=w; head[u]=ne; } void spfa() { memset(dis,oo,sizeof(dis)); int t=0,w=1; tag[0]=1; q[w]=0; dis[0]=0; while(t!=w) { int u=q[t++]; tag[u]=0; if(t==MAXN) t=0; for(int i=head[u]; i; i=e[i].next) { int v=e[i].to; if(dis[v]>dis[u]+e[i].v) { dis[v]=dis[u]+e[i].v; if(tag[v]==0) { q[w++]=v; tag[v]=1; if(w==MAXN) w=0; } } } } } int main() { scanf("%d%d",&n,&m); int nm=(n*m-n-m+1)<<1; for(int i=1; i<=n; i++) { for(int j=1; j<m; j++) { scanf("%d",&x); if(i==1) insert(j,nm+1,x); else if(i==n) insert(0,(((n-1)<<1)-1)*(m-1)+j,x); else insert(((i-1)<<1)*(m-1)+j,(((i-1)<<1)-1)*(m-1)+j,x); } } for(int i=1; i<n; i++) { for(int j=1; j<=m; j++) { scanf("%d",&x); if(j==1) insert(0,((i<<1)-1)*(m-1)+1,x); else if(j==m) insert(((i<<1)-1)*(m-1),nm+1,x); else insert(((i-1)<<1)*(m-1)+j-1,((i<<1)-1)*(m-1)+j,x); } } for(int i=1; i<n; i++) { for(int j=1; j<m; j++) { scanf("%d",&x); insert((((i-1)<<1)+1)*(m-1)+j,((i-1)<<1)*(m-1)+j,x); } } spfa(); printf("%d ",dis[nm+1]); return 0; }
奶牛的旅行
0/1分数规划
二分答案+spfa判负环
最优比率生成树
抢掠计划
把强联通分量缩点
从点1开始跑一遍最长路spfa,在酒吧取max
奶牛接力跑
倍增floyd(floyd快速幂)
g[1][i][j]表示从i到j只经过一条边的最短路
如何转移到g[2][i][j]?
枚 举所有中点k,对于所有的k,g[2][i][j]表示min(g[1][i][k]+g[1][k][j])
同理也可以求出g[4][i][k]
所以g[p][i][j]=min(g[p/2][i][k]+g[p/2][k][j])
g[3][i][j]=min(g[2][i][k]+g[1][k][j])
和快速幂的关系:用类似于快速幂的方法将其分解
while(b){ if(b&1){ memset(f,0x3f,sizeof(f)); for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ f[i][j]=min(f[i][j],ret[i][k]+g[k][j]); } } } memcpy(ret,f,sizeof(f)); } memset(f,0x3f,sizeof(f)); for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ f[i][j]=min(f[i][j],g[i][k]+g[k][j]); } } } memcpy(g,f,sizeof(f));
b>>=1; } print(ret[S][E])
Destroying road
- 两条最短路不交叉 删掉的边的数量:m-dis[s1][t1]-dis[s1][t2]
- 两条最短路有公共部分
Bfs求最短路
匈牙利算法:配对
int g[N][N]; int lk[N];// mei zi xi huan na ge nan de bool vis[N];//zhe yi lun, mei zi you mei you bei jiao huan bool find(int x){ for(int i=1;i<=n;i++){ if(!vis[i]&&g[x][i]){ vis[i]=1; if(lk[i]==0||find(lk[i])){ lk[i]=x; return 1; } } } return 0; } for(int i=1;i<=n;i++){ memset(vis,0,sizeof(vis)); if(find(i)){ hunpei++; }else{ break; } }
差分约束系统
最小化最长路
例题:糖果