图论(2017NOIP复习)

来源:互联网 发布:cocos2d 源码 编辑:程序博客网 时间:2024/05/21 07:12

1.经典最短路和分层最短路
2.生成树(多半辅助型)
3.查分约束
4.拓扑排序

以下未得及整理出来
5.强连通(tarjan)
6.二分图以及二分图匹配
7.欧拉路,欧拉回路
8.次短路,次小生成树

1.经典最短路和分层最短路

一些基本的求最短路(最长路)还是应(bi)该(xu)知道的
Floyd(n^3)
Dijkstra(n^2) 以及 Dijkstra+堆优化(nlogn)
spfa(VE)

分层最短路和经典最短路本质是一样的,无非就是从一层跑到下一层(状态转移)

对于求最短路,几种方法各有优劣
对于固定点跑最短路

对于有向图(无负环)而言,建议采用堆优化的Dijkstra其他形式的图,多半跑spfa(大概吧)

上面两种方法经常轮流被卡,所以具体情况具体分析啊

Floyd求的任意两点间的最短路,多半是辅助处理(毕竟裸的有什么意思呢)



献上裸题:
洛谷 P2939 [USACO09FEB]改造路Revamping Trails
https://www.luogu.org/problemnew/show/2939

经典的一道卡spfa的分层最短路裸题
对于每一条边,要么跑过去,要么改造跑过去,要么不跑
Dijkstra+堆优化轻松AC


其次,最短路也经常和生成树或者二分等种种结合


对于最短路题目,很多时候都是需要一些神奇的思路,个人感觉,多做一些这类题目,对于像我这样的蒟蒻orz,还是很有帮助的

例题:
洛谷 P1710 地铁涨价
https://www.luogu.org/problemnew/show/P1710

思路:倒序相加(顺嘴了),因为题目说找不到最短路就会不满意,所以价格为2的边一定不是最短路,(忘了,提前先跑一边最短路,记录最短路径),先把所有没有涨价的边加进去,然后倒着处理,每次加入的这条边(假设x<->y),如果其中一条边已经构成最短路(假设x),另一条边能够通过这条边成为最短路(假设y),从y这一点跑一边dfs(在dfs中同样判断是否满足这样的情况,统计个数)。这样一来,所有点最多跑一边。

program df;type point=^node;node=recorddate,ends:longint;next:point;end;ccdd=array[0..1000000] of point;var i,j,n,m,x,y,z,k,t,q:longint;path,pa:ccdd;a,c,e,dis,des,ans:array[0..300000] of longint;b:array[0..300000] of boolean;tm:array[0..5000000] of longint;procedure com(x,y:longint);var i:point;begini:=path[x];new(path[x]);path[x]^.ends:=y;path[x]^.next:=i;end;procedure com2(x,y:longint);var i:point;begini:=pa[x];new(pa[x]);pa[x]^.ends:=y;pa[x]^.next:=i;end;procedure spfa(p:ccdd);var i:point;h,t,y,u:longint;beginfillchar(dis,sizeof(dis),$7f);fillchar(b,sizeof(b),false);h:=0; t:=1; tm[1]:=1; dis[1]:=0;repeatinc(h);u:=tm[h];i:=p[u];b[u]:=false;while i<>nil dobeginy:=i^.ends;if dis[y]>dis[u]+1 thenbegindis[y]:=dis[u]+1;if not b[y] thenbegininc(t);tm[t]:=y;b[y]:=true;end;end;i:=i^.next;end;until h=t;end;procedure dfs(x:longint);var i:point;y:longint;begindis[x]:=des[x];inc(t);i:=path[x];while i<>nil dobeginy:=i^.ends;if (dis[y]<>des[y]) and (des[y]=dis[x]+1) then dfs(y);i:=i^.next;end;end;beginreadln(n,m,q);for i:=1 to m dobeginreadln(a[i],c[i]);com2(a[i],c[i]);com2(c[i],a[i]);end;for i:=1 to q dobeginread(e[i]);b[e[i]]:=true;end;for i:=1 to m doif not b[i] thenbegincom(a[i],c[i]);com(c[i],a[i]);end;spfa(pa);des:=dis;spfa(path);for i:=1 to n doif dis[i]<>des[i] then inc(ans[q]);for i:=q downto 1 dobeginx:=a[e[i]]; y:=c[e[i]];com(x,y);com(y,x);t:=0;if (dis[x]=des[x]) and (dis[y]<>des[y]) and (des[y]=dis[x]+1) then dfs(y);if (dis[y]=des[y]) and (dis[x]<>des[x]) and (des[x]=dis[y]+1) then dfs(x);ans[i-1]:=ans[i]-t;end;for i:=1 to q dowriteln(ans[i]);end.

3.差分约束
一:差分约束建图方法
第一:
感觉难点在于建图
第二:
①:对于差分不等式,a - b <= c ,建一条 b 到 a 的权值为 c 的边,求的是最短路,得到的是最大值
②:对于不等式 a - b >= c ,建一条 b 到 a 的权值为 c 的边,求的是最长路,得到的是最小值
③:存在负环的话是无解
④:求不出最短路(dis[ ]没有得到更新)的话是任意解

1.如果要求最大值想办法把每个不等式变为标准x-y<=k的形式,然后建立一条从y到x权值为k的边,变得时候注意 x-y<k =>x-y<=k-1
如果要求最小值的话,变为x-y>=k的标准形式,然后建立一条从y到x的k边,求出最长路径即可
2.如果权值为正,用dj,spfa,bellman都可以,如果为负不能用dj,并且需要判断是否有负环,有的话就不存在

小技巧:一般情况都会建一个源点(0或者n+1),源点向每一个点建立一条边,但是建的边一定要让第一个点和第n个点先跑,可以防止被卡(数据为链的话,就跟卡spfa是一样的)

例题:洛谷 P3275 [SCOI2011]糖果(非常全面的一题了)
https://www.luogu.org/problemnew/show/P3275


最终要求最小值,即跑最长路,即变为x-y>=K 的标准形式


X=1,要求糖果数一样多,即互相建一条权值为0的边X=2,要求A<B,变为B-A>0,再变为B-A>=1A向B建1的边X=3,要求A>=B,即B向A0的边X=4,要求A>B,变形为A-B>=1,B向A1的边X=5,要求A<=B,即A向B建0的边这一题建源点的时候就必须让1和n最近访问(我用链表写的,所以最后建边1和n),否则面对链状数据只能RE

4.拓扑排序
其实就是跑一个有先后顺序的图(即DAG),有时候题目会要求让你想访问尽量小的或尽量大的,这时候就可以堆优化


普通的拓扑排序用队列储存元素,每次添加删除即可(太裸了)
大多数都是题目要求先后顺序,所以用小根堆或者大根堆存储就好了

裸题就不上了
先来一题稍难一点的(个人认为)

vijos 1790 拓扑编号
https://vijos.org/p/1790
描述

H国有n个城市,城市与城市之间有m条单向道路,满足任何城市不能通过某条路径回到自己。
现在国王想给城市重新编号,令第i个城市的新的编号为a[i],满足所有城市的新的编号都互不相同,并且编号为[1,n]之间的整数。国王认为一个编号方案是优美的当且仅当对于任意的两个城市i,j,如果i能够到达j,那么a[i]应当小于a[j]。
优美的编号方案有很多种,国王希望使1号城市的编号尽可能小,在此前提下,使得2号城市的编号尽可能小…依此类推。
格式

输入格式

第一行读入n,m,表示n个城市,m条有向路径。
接下来读入m行,每行两个整数:x,y
表示第x个城市到第y个城市有一条有向路径。
输出格式

输出一行:n个整数
第i个整数表示第i个城市的新编号a[i],输出应保证是一个关于1到n的排列。
样例1

样例输入1

5 4
4 1
1 3
5 3
2 5
Copy
样例输出1

2 3 5 1 4
Copy
限制

每个测试点1s
提示

30%的测试点满足:n <= 10, m <= 10
70%的测试点满足:n <= 1000, m <= 10000
100%的测试点满足:n <= 100000, m <= 200000
输入数据可能有重边,可能不连通,但保证是有向无环图。

题目说让前面的编号尽量小,如果正向处理的话,是没办法保证的
样例中 4->1->3,2->5->3,题目要求在保证1的编号小的同时再保证2的编号小(先编号4,再编号1,再编号2),
因此无法贪心处理,但是 不难发现(额~) 如果逆向处理的话,保证后面的城市编号较大,就可以同时保证前面的城市编号小,所以选择逆向建边,最后反向处理编号即可

program df;type point=^node;node=recorddate,ends:longint;next:point;end;var i,j,n,m,x,y,z,k,t,len:longint;path:array[0..200000] of point;a,dis,d,f:array[0..200000] of longint;procedure com(x,y:longint);var i:point;begini:=path[x];new(path[x]);path[x]^.ends:=y;path[x]^.next:=i;end;procedure put(x:longint);var i,j:longint;begininc(len);a[len]:=x;i:=len;while (i>1) and (a[i div 2]<a[i]) dobeginj:=a[i]; a[i]:=a[i div 2]; a[i div 2]:=j;i:=i div 2;end;end;function get:longint;var i,j,dd:longint;beginget:=a[1];a[1]:=a[len];dec(len);i:=1;while i*2<=len dobeginif (i*2+1>len) or (a[i*2+1]<a[i*2]) then j:=i*2else j:=i*2+1;if a[i]<a[j] thenbegindd:=a[i]; a[i]:=a[j]; a[j]:=dd;i:=j;endelse break;end;end;procedure dfs;var y,u:longint;i:point;beginwhile len>0 dobeginu:=get;inc(t); dis[t]:=u;i:=path[u];while i<>nil dobeginy:=i^.ends;dec(d[y]);if d[y]=0 then begin put(y); d[y]:=maxlongint div 3; end;i:=i^.next;end;end;end;beginreadln(n,m);for i:=1 to m dobeginreadln(x,y);com(y,x);inc(d[x]);end;len:=0; t:=0;for i:=1 to n  doif d[i]=0 then begin put(i); d[i]:=maxlongint div 3; end;  dfs;for i:=1 to t dof[dis[i]]:=n-i+1;for i:=1 to t dowrite(f[i],' ');end.
原创粉丝点击