图论

Tags:Noip前的大抱佛脚

知识点

二分图相关

DFS找环

From [CodeForces19E] Fairy

环一定是DFS树上的返祖边

通过对偶环+1,奇环-1可以找到出现在所有奇环上的一条边

这种方法也可以求图中最多的不相交的圆的个数

并查集维护二分图

在数据结构有讲,用按秩合并的带权并查集维护黑白颜色即可

二分图匹配的不可行边

求一定不会出现在二分图最大匹配中的边

方法是:跑网络流,对残余网络跑\(Tarjan\)缩点,一条边不是不可行边当且仅当它是匹配边或者他们被缩在同一个点中

感性理解:缩在同一点,把环给反向,于是就可以变成匹配边了

注意:Dinic跑二分图复杂度为\(O(\sqrt n m)\)!!

最小生成树相关

最短路树

要求生成树中每个点到1号点的距离等于图中最短路,且生成树边权之和最小

这个直接用SPFA或者Dijkstra求从哪里来是最短路,然后贪心选个最短的就好了

最短路相关

负环

可以使用SPFA,先同时把所有点入队,然后判断一个点的最短路如果经过超过\(n\)个点,则表示存在负环

多源最短路

把源点同时入队/入堆,然后照样跑即可,同时如果需要可以记一个from表示从哪个源点过来的

如果不需要记录,新建源点向所有源点连0的边即可

差分约束系统

连边以及跑最长/短路

对于一些形如 \(v_a\le v_b+W\) 的若干关于"\(\le\)“的式子,可以从从\(b\)向\(a\)连一条权值为\(W\)的边,然后跑最短路

最后跑出来的答案\(dis[a]\)表示的是关于\(v_a\)的若干不等式的解,就是说不等式组中最小的那个,所以\(dis[a]\)是\(v_a\)的最大值

如果是要求最小值,那么对于上面的式子,可以从\(a\)向\(b\)连\(-W\)的边,跑最长路,得出的\(dis[b]\)是\(b\)的最小值

无解以及做题方法

可以通过存在正/负环判断无解

很多时候模型并不明确,可以二分答案之后判断合法性来求解一般问题

01最短路

边权只有0或1的最短路,感觉是Dijkstra+SPFA,操作是开一个双端队列deque,每次更新最短路,如果由+0更新就丢队头,+1更新就丢队尾,然后每个点只往外面更新一次,其实是用队列模拟Dij的堆,这样复杂度是\(O(m)\)的

k短路

  • 求一张有向图中\(1\)到\(n\)中第\(k\)短的路径,可以不是简单路径(有环,自环,重边)

    做法1:A*搜索

    建出以\(n\)为根的最短路树,那么每个点有两个参数:\(f[i]\)表示到达\(i\)点已经走了\(f[i]\)的长度,\(h[i]\)表示\(i\)的估价函数,表示\(i\)到\(n\)最少还要走多远,以这个为权值每次在堆中选取权值最小的更新,\(n\)被走到的第\(k\)次即为\(k\)短路

    做法2:可持久化左偏树

    建出最短路树后我们发现对于任意一条路径,可以拆成树边和非树边,且一个非树边的边集对应着一条路径,于是我们需要求出第\(k\)小的非树边集

    令\(dis[i]\)表示\(i\)到\(n\)的最短路长度,则定义一个非树边集\(S\)的权值为\(dis[1]+\sum_{e\in S}d[e],d[e]=dis[to]+e.w-dis[fr]\),\(d[e]\)的实际含义是走这条边新增的代价,于是把这个丢进优先队列里取出\(k\)次就好了

    然后我们考虑怎么得到非树边集&怎么拓展新状态。

    对于每个点,维护该点到树根(n)的路径的所有非树边的出边集,并按照出边的\(d[e]\)排序(这个用可持久化左偏树得到,每次从父亲那里copy下来,代码见魔法猪学院)

    定义一个全局的优先队列按照\(bs+val\)排序,\(bs\)表示已经走了的边的权值,\(val\)表示当前边集的权值。首先把1号点的左偏树树顶丢入全局堆中,然后从堆中取出k次元素得到第k小

    对于一个状态是当前最小,由此可以拓展出三个新状态:该点的左偏树中的左右儿子(表示为从该点向上不选择原来的非树边,转而选择一条比它大一点的非树边。需要堆中父亲一定大于左右儿子的性质做保证)、该条边指向的点的堆顶(表示为一定选这条边,在往n走的路径中再选一条非树边,此时要给\(bs_{new}+=bs_{old}+val_{old}\))

    复杂度:\(O(nlogn+mlogm+klogk)\),分别为最短路、左偏树、优先队列的复杂度之和

网络流

zkw费用流

用SPFA跑出最短路之后,用类似Dinic的dfs跑很多遍,对于相同费用的路径较多的图的效率特别高,当然不满足这种性质效率就会比一般的费用流效率低(无限之环加上zkw费用流快了100倍!)

int dfs(int x,int flow)
{
if(x==T) return flow;vis[x]=1;
for(int &i=cur[x];i;i=a[i].next)
{
int R=a[i].to;//dis表示的费用最短路
if(!a[i].w||dis[R]!=dis[x]+a[i].cost||vis[R]) continue;
int k=dfs(R,min(flow,a[i].w));
if(k) {a[i].w-=k,a[i^1].w+=k;vis[x]=0;return k;}
}
return 0;//如果在这个点找不到增广路,vis就只会在SPFA中清空,这个点在本次dis数组时不会再访问
}
while(SPFA())
{
for(int i=1;i<=T;i++) cur[i]=head[i];
while(int tmp=dfs(S,inf)) mxfl-=2*tmp,ans+=tmp*dis[T];
}

做题经验

同余类最短路

来源:墨墨的等式

求\(a_1x_1+a_2x_2+...+a_nx_n=B\),其中\(x\)都有非负整数解,求\(B\)在一定范围内的取值个数

巧妙地把每个解归类为\(\%a_1\)的余数那个点,对每个点往外连边跑最短路,表示得到\(\%a_1=k\)的最小的\(B\)是多大,从而推出\(B\)的个数

边权是max的形式

代价是进入该点和从该点出去的边权\(max\),求\(1\)到\(n\)的最小代价

注意题目是无向图,所以可以每条边拆成两个,然后左右对应,差分建图

其实遇到max都应该要想想差分

图论模型的转换

遇到以下情况可以考虑图论模型来连边

  • 有两类点,两类点之间有关联,然后可以考虑把A类点像B类点的两条边转为B类点的一条边(ZKJ太阳月亮匹配/棋盘上的守卫)
  • 两个点相互关联,知道其中一个就可以推导另一个
  • 一个物品有两个权值,权值值域一定(ZKJ烟火左右权可翻转,求最大权连续序列)

边定向

这是一种思考方向,可以参考[BZOJ4883]棋盘上的守卫

树上点覆盖

一个关键点能够覆盖距离它不超过k的点,要求覆盖所有点的最小关键点数

这题是贪心不是DP啊QAQ

两树叠图的最小割及方案数

答案为2或者3,所以一定是A树断一边,B树断若干边构成的

把A作为树形结构,B树的每条边\(tag[x]++,tag[y]++,tag[lca(x,y)]-=2\)

所以子树之和便是子树内向外面连的边数之和

一类BFS最小生成树做法

给定网格图/树结构,求一些关键点的最小生成树(10.15YLT3)

方式是以关键点为源跑多源最短路,一个点被经过的第二次/一条边连接的两点的最近源点不同,就可以连接这两个源点了。注意边数还是原来的边数级别的

图论模板库

Tarjan相关

强连通分量:有向图中任意两个顶点都有相互到达的路径的一个极大子图

边双连通分量:一个子图中删去任意一条边都不影响图的连通性

点双连通分量:一个子图中删去任意一个点都不影响图的连通性

割边:连接两个边双的边

割点:连接两个点双的点

割边

//把一个边双缩点
void Tarjan(int x)
{
vis[x]=1;sta[++top]=x;
dfn[x]=low[x]=++tot;
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;
if(!dfn[R]) Tarjan(R),low[x]=min(low[x],low[R]);
else if(vis[R]) low[x]=min(low[x],low[R]);
}
if(low[x]!=dfn[x]) return;
for(int k=sta[top],lst=0;lst!=x;lst=k,k=sta[--top])
vis[k]=0,bel[k]=x;
}
//无向图缩点略有不同

割点

//求割点(tag[x]=1)
void Tarjan(int x,int f)
{
int s=0;dfn[x]=low[x]=++tot;
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;if(R==f) continue;
if(!dfn[R])
{
s++;Tarjan(R,x);tag[x]|=low[R]>=dfn[x];
low[x]=min(low[x],low[R]);
}
else low[x]=min(low[x],dfn[R]);//注意!这里必须是dfn,有反例!!
}
if(!f&&s==1) tag[x]=0;
}

有向图缩点后成为DAG,无向图缩点/求割点后成为树的结构

圆方树

int n,m,dfn[N],low[N],sta[N],top,node,tot,siz[N];
void Min(int &a,int b) {if(b<a) a=b;}
void Tarjan(int x)
{
dfn[x]=low[x]=++tot;sta[++top]=x;siz[x]=-1;
for(int i=A.head[x];i;i=A.a[i].next)
{
int R=A.a[i].to;
if(dfn[R]) {Min(low[x],dfn[R]);continue;}
Tarjan(R);Min(low[x],low[R]);
if(low[R]>=dfn[x])
{
B.link(++node,x);siz[node]=1;
for(int k=sta[top],lst=0;lst!=x;lst=k,k=sta[--top])
B.link(node,k),siz[node]++;
}
}
//node初值为n
}

2-SAT

具体作用。。。不可描述。。。

大概就是给出每个点选或不选使得满足所有条件吧。

输出方案的话选择超级点编号小的那个选择输出

struct edge{int next,to;}a[N];
int n,m,low[N],dfn[N],tot,sta[N],top,bel[N],node,in[N],head[N],cnt;
int rev(int x) {return x>n?x-n:x+n;}
void Min(int &a,int b) {if(b<a) a=b;}
void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;}
void Tarjan(int x)
{
dfn[x]=low[x]=++tot;sta[++top]=x;in[x]=1;
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;
if(!dfn[R]) Tarjan(R),Min(low[x],low[R]);
else if(in[R]) Min(low[x],low[R]);
}
if(low[x]!=dfn[x]) return;node++;
for(int lst=0,k=sta[top];lst!=x;lst=k,k=sta[--top])
bel[k]=node,in[k]=0;
}
int main()
{
cin>>n>>m;
for(int i=1,x,a,y,b;i<=m;i++)
{
scanf("%d%d%d%d",&x,&a,&y,&b);
x+=a*n;y+=b*n;
link(rev(x),y);link(rev(y),x);
}
for(int i=1;i<=n*2;i++) if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++)
if(bel[i]==bel[rev(i)]) return puts("IMPOSSIBLE"),0;
puts("POSSIBLE");
for(int i=1;i<=n;i++)
printf("%d ",bel[i]<bel[rev(i)]?0:1);
}

TarjanLCA

\(O(n*反阿克曼函数+Q)\)离线求lca

步骤:

  • 对询问挂链(vector),初始化并查集
  • \(dfs\)整棵树后处理询问,扫完该子树才将其并查集父亲指向树结构的父亲
  • 如果对于一个询问\((x,y)\),当前扫到\(y\)且\(x\)已经被访问过,答案为x的并查集父亲
void tarjan(int x,int fr)
{
for(int i=head[x];i;i=a[i].next)
if(R!=fr) tarjan(R,x);vis[x]=1;
for(int i:U[x])
{
int y=g[i]^x,&s=ans[i];//g[i]=x^y,表示第i个询问
if(vis[y]) s=find(y);
}
fa[x]=fa[fr];
}

最短路相关

SPFA判负环

方式是先把所有点入队了,然后若有一个点的最短路超过n个点即存在负环

复杂度\(O(n^2)\)

注意判自环!

for(int i=1;i<=n;i++) Q.push(i),vis[i]=1;
while(!Q.empty())
{
int x=Q.front();
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;
if(dis[R]<=dis[x]+a[i].w) continue;
dis[R]=dis[x]+a[i].w;
f[R]=f[x]+1;
if(f[R]==n+1) return 1;
if(!vis[R]) Q.push(R),vis[R]=1;
}
Q.pop();vis[x]=0;
}

Dijkstra

稳定的\(O(nlogn)\)单源最短路做法

struct Node
{
int x;ll dis;
int operator < (const Node&A)const
{return dis>A.dis;}
};
int n,m,vis[N],head[N],cnt,S;ll dis[N];
priority_queue<Node> Q;
void Dijkstra()
{
memset(dis,127,sizeof(dis));
Q.push((Node){S,0});dis[S]=0;
while(!Q.empty())
{
int x=Q.top().x;Q.pop();
if(vis[x]) continue;vis[x]=1;
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;
if(dis[R]<=dis[x]+a[i].w) continue;
dis[R]=dis[x]+a[i].w;
Q.push((Node){R,dis[R]});
}
}
}

网络流

最大流

\(Dinic\)算法\(O(n^3)\),二分图\(O(m\sqrt n)\)

const int N=1e4+10,inf=1e9;
struct edge{int next,to,w;}a[N*21];
int n,m,S,T,dep[N],head[N],cnt=1,cur[N],ans;
queue<int> Q;
void link(int x,int y,int w)
{
a[++cnt]=(edge){head[x],y,w};head[x]=cnt;
a[++cnt]=(edge){head[y],x,0};head[y]=cnt;
}
int BFS()
{
memset(dep,0,sizeof(dep));
Q.push(S);dep[S]=1;
while(!Q.empty())
{
int x=Q.front();Q.pop();
for(int i=head[x];i;i=a[i].next)
if(!dep[a[i].to]&&a[i].w) dep[a[i].to]=dep[x]+1,Q.push(a[i].to);
}
return dep[T];
}
int DFS(int x,int flow)
{
if(x==T) return flow;
for(int &i=cur[x];i;i=a[i].next)
{
int R=a[i].to;
if(!a[i].w||dep[R]!=dep[x]+1) continue;
int k=DFS(R,min(flow,a[i].w));
if(k) {a[i].w-=k;a[i^1].w+=k;return k;}
}
return 0;
}
int main()
{
cin>>n>>m>>S>>T;
for(int i=1,x,y,w;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&w);
link(x,y,w);
}
while(BFS())
{
for(int i=1;i<=n;i++) cur[i]=head[i];
while(int tmp=DFS(S,inf)) ans+=tmp;
}
cout<<ans<<endl;
return 0;
}

费用流

struct edge{int next,to,w,c;}a[N*20];
int n,m,S,T,head[N],cnt=1,dis[N],pe[N],px[N],vis[N];
ll ans,f;queue<int> Q;
void link(int x,int y,int w,int c)
{
a[++cnt]=(edge){head[x],y,w,c};head[x]=cnt;
a[++cnt]=(edge){head[y],x,0,-c};head[y]=cnt;
}
int SPFA()
{
memset(dis,63,sizeof(dis));
Q.push(S);dis[S]=0;vis[S]=1;
while(!Q.empty())
{
int x=Q.front();
for(int i=head[x];i;i=a[i].next)
{
int R=a[i].to;
if(dis[R]<=dis[x]+a[i].c||!a[i].w) continue;
dis[R]=dis[x]+a[i].c;
pe[R]=i;px[R]=x;
if(!vis[R]) vis[R]=1,Q.push(R);
}
Q.pop();vis[x]=0;
}
return dis[T]!=dis[0];
}
int main()
{
cin>>n>>m>>S>>T;
for(int i=1,x,y,w,f;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&w,&f);
link(x,y,w,f);
}
while(SPFA())
{
int flow=1e9;
for(int i=T;i!=S;i=px[i]) flow=min(flow,a[pe[i]].w);
for(int i=T;i!=S;i=px[i]) a[pe[i]].w-=flow,a[pe[i]^1].w+=flow;
f+=flow,ans+=flow*dis[T];
}
cout<<f<<" "<<ans<<endl;
}

Noip前的大抱佛脚----图论的更多相关文章

  1. Noip前的大抱佛脚----文章索引

    Noip前的大抱佛脚----赛前任务 Noip前的大抱佛脚----考场配置 Noip前的大抱佛脚----数论 Noip前的大抱佛脚----图论 Noip前的大抱佛脚----动态规划 Noip前的大抱佛 ...

  2. Noip前的大抱佛脚----Noip真题复习

    Noip前的大抱佛脚----Noip真题复习 Tags: Noip前的大抱佛脚 Noip2010 题目不难,但是三个半小时的话要写四道题还是需要码力,不过按照现在的实力应该不出意外可以AK的. 机器翻 ...

  3. Noip前的大抱佛脚----字符串

    目录 字符串 经验 用FFT求解字符串匹配问题 两(多)串DP时状态合并 最长公共子序列转LIS 位运算最大值 挂链哈希 哈希处理回文串 树哈希 字符串模板库 KMP 最小循环表示 Mancher A ...

  4. Noip前的大抱佛脚----一些思路

    目录 一些思路 序列 函数问题 网格图 删除和询问 乘法问题 顺序问题 最值问题 研究成果 数论分块套数论分块的复杂度 一些思路 Tags:Noip前的大抱佛脚 序列 线段树(当然还要有主席树啊!) ...

  5. Noip前的大抱佛脚----数论

    目录 数论 知识点 Exgcd 逆元 gcd 欧拉函数\(\varphi(x)\) CRT&EXCRT BSGS&EXBSGS FFT/NTT/MTT/FWT 组合公式 斯特林数 卡塔 ...

  6. Noip前的大抱佛脚----数据结构

    目录 数据结构 知识点及其应用 线段树 神奇标记 标记不下放 并查集 维护二分图 维护后继位置 堆 可并堆的可持久化 dsu on tree 方式&原理 适用范围 单调队列 尺取合法区间 模板 ...

  7. Noip前的大抱佛脚----赛前任务

    赛前任务 tags:任务清单 前言 现在xzy太弱了,而且他最近越来越弱了,天天被爆踩,天天被爆踩 题单不会在作业部落发布,所以可(yi)能(ding)会不及时更新 省选前的练习莫名其妙地成为了Noi ...

  8. Noip前的大抱佛脚----根号对数算法

    根号算法 分块 数列分块入门九题(hzwer) 入门题1,2,3,4,5,7 问题:给一段区间打上标记后单点查询 解法:主要是每块维护一些标记,计算答案等,此类分块较为简单 注意:块大小一般为\(\s ...

  9. Noip前的大抱佛脚----奇技淫巧

    STL函数 set set查找前驱后继 multiset<int>::iterator iter; S.insert(x); iter=S.find(x);//返回迭代器 iter--;/ ...

随机推荐

  1. springCloud微服务入门

    目录 前言 Eureka 注册中心server 新建 配置 服务提供者service 新建 配置 服务消费者controller 新建 配置 使用 Feign负载均衡 前言 springCloud是一 ...

  2. 使用 CLI 创建 Azure VM 的自定义映像

    自定义映像类似于应用商店映像,不同的是自定义映像的创建者是你自己. 自定义映像可用于启动配置,例如预加载应用程序.应用程序配置和其他 OS 配置. 在本教程中,你将创建自己的 Azure 虚拟机自定义 ...

  3. Oracle EBS 多节点停应用

    adstpall.sh   -mode=allnodes app/apps

  4. Loadrunner11代理录制&各个常见功能介绍

    1.代理录制: Lr代理工具:C:\Program Files (x86)\HP\LoadRunner\bin\wplus_init_wsock.exe 1) 设置代理 配置代理信息: 2)设置浏览器 ...

  5. 利用jTessBoxEditor工具进行Tesseract3.02.02样本训练,提高验证码识别率

    1.背景 前文已经简要介绍tesseract ocr引擎的安装及基本使用,其中提到使用-l eng参数来限定语言库,可以提高识别准确率及识别效率. 本文将针对某个网站的验证码进行样本训练,形成自己的语 ...

  6. 面向对象课程 - 寒假第三次作业 - C++计算器项目初始部分

    C++计算器项目初始部分 零.项目源文件地址 传送门:calculator 一.项目信息相关: 项目:Calculator 版本:1.0 日期:2016.2.16 实现: 基本的操作界面 对四则运算表 ...

  7. 面向对象程序设计_tesk1_寒假伊始

    大一下学期的自我目标(要求包含对大一上学期的总结.对面向对象课程完成后学习到的能力的预期,对面向对象课程的期望.对编程和专业能力的愿景规划) 在大学的第一个学期,相信很多人都是在得过且过度过,我也不例 ...

  8. 将本地已有项目上传到github

    1.在github上创建一个文件 2.看本地C盘中是否有.ssh文件夹 (C:\Users\用户名\.ssh) 检测有没有.ssh文件夹:执行命令   cd ~/.ssh 如果没有的话执行git命令: ...

  9. Cocos2D-x-3.0 编译(Win7)

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/logotostudent/article/details/25425969 第一次開始用手游引擎挺激 ...

  10. MySQL IFNULL基本用法

    MySQL IFNULL函数是MySQL控制流函数之一,它接受两个参数,如果不是NULL,则返回第一个参数. 否则,IFNULL函数返回第二个参数. 两个参数可以是文字值或表达式. 以下说明了IFNU ...