[SinGuLaRiTy] 图论题目复习
Posted SinGuLaRiTy2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SinGuLaRiTy] 图论题目复习相关的知识,希望对你有一定的参考价值。
【SInGuLaRiTy-1027】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
[Vijos 1423] 最佳路线
题目描述
年久失修的赛道令国际汽联十分不满。汽联命令主办方立即对赛道进行调整,否则将取消其主办权。主办方当然必须马上开始行动。
赛道测评人员经过了三天三夜的数据采集,选出了若干可以使用的道路和各道路行驶所需的时间。这些道路包括若干直道和弯道,每个直道连接两个不同的弯道且为单向,两个弯道之间可能有多条直道,通过直道和弯道都需要一定的时间。主办方打算在这些可用道路中选出一部分作为赛道。赛道是由直道和弯道交替组成的一圈,赛道可多次经过同一条弯道,因为主办方可以通过架设立交桥的方法避免撞车。为了使比赛更加精彩,主办方希望选择一条单圈时间最短的赛道,由于观众席的位置在弯道1,所以赛道必须经过弯道1(赛道至少要包含一条直道)。
输入
第一行是两个整数n,m(1<=n<=200,1<=m<=100000),分别表示弯道数和直道数。接下来n行,第i行是一个整数ai(1<=ai<=1000),表示通过第i个弯道所消耗的时间。接下来m行,第j行是三个整数xj,yj,bj(1<=xj,yj<=n,1<=bj<=1000),表示从弯道xj到弯道yj有一条单向直道,且通过该直道所消耗的时间为bj。
输出
一个整数s,表示单圈时间最短的赛道的单圈时间,若无解则输出-1。
样例数据
样例输入 | 样例输入 |
3 6 |
13 |
解析
我用的是Floyd:t[i]表示第i个弯道时间,d[i][j]表示第i个弯道到第j个弯道时间,初始化INF。接着运用floyd求出各个弯道之间直道最短时间,记得要加上连接弯道的直道时间。题目要求要经过1弯道的最短路且必要有一直道路,说明必要有两个弯道连接而成加上直道,则枚举2到n所有点,取其d[1][i]+t[i]+d[i][1]+t[1]最小值。还需要判断是否存在解即判断与INF的关系。
如果数据再强一点的话,就该用两次Dijkstra(正向反向各一次),然后枚举中间点来选取答案。
Code
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<algorithm> #define maxn 1010 #define INF 0x3f3f3f3f int n,m,a,b,c; int dist[maxn][maxn]; int ans=INF; int t[maxn]; using namespace std; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&t[i]); for(int i=1;i<=m;i++) for(int j=i+1;j<=n;j++) dist[i][j]=dist[j][i]=INF; for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); dist[a][b]=min(c+t[a],dist[a][b]); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]); for(int i=2;i<=n;i++) ans=min(ans,dist[1][i]+dist[i][1]); if(ans<INF) printf("%d",ans); else puts("-1"); return 0; }
[COCI 2011-2012 Contest #7] 总统演讲 (KAMPANJA)
题目描述
临近选举,总统要在城市1和城市2举行演讲。他乘汽车完成巡回演讲,从1出发,途中要经过城市2,最后必须回到城市1.特勤局对总统要经过的所有城市监控。为了使得费用最小,必须使得监控的城市最少。求最少要监控的城市。
一共有N个城市,有M条有向边。(0<=N,M<=200)
有N从1出发途中要经过2,最后要回到1的路线中最少要经过的点的数目。
输入
第一行包含2个整数N,M。N表示城市的数目,M表示有向边的数目。
接下来m行,每行两个数A,B,表示从A到B有一条有向边。
输出
最少要监控的城市的数量。
样例数据
样例输入 | 样例输出 |
6 7 |
6 |
解析
首先,对于任意两座城市,我们要算出distance[a][b],即从城市a到城市b的距离,如果两座城市之间没有路径,我们就将distance设置为INF。对于这一操作,Floyd就可以解决了。
我们就可以用dp[a][b]来表示路径1->a->b->1上监控的最少城市数量(注意:a和b也可以相同)。接下来,我们就可以用以下式子来完成dp[a][b]到dp[x][y]的状态转移了:
dp[x][y]=dp[a][b]+distance[b][x]+distance[x][y]+distance[y][a]-1
如果我们使用Dijkstra对所有的(a,b)状态进行搜索,我们的目标状态就是dp[2][2]。
Code
#include<algorithm> #include<cstdio> #define MAXN 210 #define INF 1000000 using namespace std; int g[MAXN][MAXN]; int dist[MAXN][MAXN]; int n, m; char done[MAXN][MAXN]; int main() { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) for(int j=0;j<n;j++) g[i][j]= (i==j ? 0 : INF) ; for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); g[--u][--v]=1; } for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) g[i][j]=min(g[i][j],g[i][k]+g[k][j]); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { dist[i][j]=INF; done[i][j]=0; } dist[1][1]=1; for(;;) { int a=-1,b=-1; for(int i=0;i<n;i++) for(int j=0;j<n;j++) { if(done[i][j]) continue; if(a==-1||dist[i][j]<dist[a][b]) { a=i; b=j; } } if(a==0&&b==0) break; done[a][b]=1; for(int c=0;c<n;c++) for(int d=0;d<n;d++) { if(c==a||c==b||d==a||d==b) continue; dist[c][d]=min(dist[c][d],dist[a][b]+g[b][c]+g[c][d]+g[d][a]-1); } } printf("%d", dist[0][0]); return 0; }
[COCI 2011-2012 Contest #7] 送票 (SETNJA)
题目描述
Mirko要给朋友们送音乐会门票。朋友的家可表示为二维平面的网格。Mirko在行走时,可以朝8个方向移动,每次都是整数坐标。他每一步朝上、下、左、右以及4个对角方向走一格。
每个朋友的家可表示为平面上的点(x,y)。当Mirko到某朋友家送票时,朋友也可以走出来迎接他,也可以向8个方向行走。因此,Mirko和这个朋友最多可以在距离他家P步远的位置相遇。P随每个朋友不同。
Mirko的起始位置与终点位置都未知。
题目要求求出:Mirko送完所有票的最少移动步数。
输入
第1行:Mirko的朋友数N (2 ≤ N ≤ 200 000)
接下来N行,每行3个数,表示: x, y, and P (0 ≤ x, y, P ≤
200 000). 朋友按Mirko送票的顺序给出
输出
第1行:1个整数,表示Mirko必须走的最少移动步数
样例数据
样例输入 | 样例输出 |
3 |
4 |
解析
每一个Mirko的朋友都是由X,Y,P描述的。注意到,与一个朋友的可能的会合点形成了一个以(X,Y)为中心的正方形,更确切地说,是一个相对顶点在(X-P,Y-P)和(X+P,Y+P)的正方形。
那么,现在这个问题就被简化为寻找按顺序经过所有正方形的最短可能路线。
对于K (1<=K<=N),我们考虑按顺序访问正方形1~K的所有可能的最短路径。设S(K)为这些路径的最终节点的集合,即我们以最佳策略访问前K个朋友后可以结束的所有位置。我们再设d(K)为这些最短路径的长度。
注意到,S(1)的点所组成的形状恰好是第一个朋友所对应的正方形,因为我们从这个正方形的任何点都可以开始(并结束)对这个朋友的访问,且当前的路径长度为0。
我们的程序要继续找到S(2)、S(3)......S(N)。我们会发现,所有的集合都是矩形。那么现在,我们就要看看怎样由S(K)找到S(K+1)。
令D表示S(K)中的任意一点到S(K+1)(即我们下一个要访问的人)的最短距离。那么d(K+1)=d(K)+D。
如果我们在四个方向上对矩形S(K)扩展D个单位,我们得到最佳访问前K个朋友再向任意方向移动D个单位后可以到达的所有点。因此,扩展的矩形与第(K+1)个朋友所对应的正方形的交集就是我们移动d(K)+D(根据D的定义,这个交集一定不是空集)个单位距离后能与第(K+1)个朋友会面的点。那么,这个交集就恰好是S(K+1)。由于这个交集是由一个矩形和一个正方形得到的(正方形和矩形的交点都与相同的轴对其),这个交集就一定是一个矩形。(还是搞不懂?画画图吧)
最终的解就只是在寻找S(2),S(3),......,S(N)时获得的所有D值的和,这恰好就是d(N)。
Code
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; struct rectangle { int x1,x2,y1,y2; rectangle(){} rectangle(int x,int y,int p) { x1=x-p; x2=x+p; y1=y-p; y2=y+p; } }; int main() { int n; scanf("%d",&n); int x,y,p; scanf("%d%d%d",&x,&y,&p); rectangle S(x,y,p); long long ans=0; for(int i=1; i<n; ++i) { scanf("%d%d%d",&x,&y,&p); rectangle A(x,y,p ); int D=max(max(A.x1-S.x2,S.x1-A.x2),max(A.y1-S.y2,S.y1-A.y2)); if(D<0) D=0; ans+=D; S.x1-=D; S.x2+=D; S.y1-=D; S.y2+=D; rectangle Part; Part.x1=max(S.x1,A.x1); Part.x2=min(S.x2,A.x2); Part.y1=max(S.y1,A.y1); Part.y2=min(S.y2,A.y2); S=Part; } cout<<ans; return 0; }
[HDU 2196] 最长链 (Computer)
题目描述
给定一棵有n个节点的树,求每个节点到其他节点的最大距离。
输入
输入第一行是一个自然数n(n≤10000);
接下来 (n−1) 行描述:第i行包含两个自然数 , 表示编号为i的节点连接到的节点编号和这条网线的长度..距离总长不会超过10^9. 每行中的两个数字用空格隔开。
输出
输出包含n行. 第i行表示对于离编号为i的节点最远的节点与该节点的距离Si(1≤i≤n)。
样例数据
样例输入 | 样例输出 |
3 1 1 1 2 |
2 3 3 |
数据规模
30% n≤100
100% n≤10000
解析
这道题是考试前一天课上讲的题目,如果我没记错的话应该是HDU 2196的原题。让我兴奋的是,尽管这道题没有被布置进作业当中,但我昨天就已经在HDU上AC了这道题,再打一遍代码也是相当的轻松,一种优越感油然而生......
下面,让我们来看看这道题的思路:对于任何一个节点M,我们可以将距离它最远的点的位置分为两类:①在以M为根节点的子树中,我们假设此种情况下的最远距离为maxdis_down;②在以M为根节点的子树之外,我们假设此种情况下的最远距离为maxdis_up。显而易见的是,此时这个点的最远距离ans[M]就为max(maxdis_down,maxdis_up)。我们重点看看第二种情况:maxdis_up=M到其父亲节点L的距离dis(M,L)+ans[L]。对此,我们可以定义一个dp[MAXN][3]的数组:
f[i][0]表示顶点为i的子树中,距离顶点i的最长距离(对应第一种情况);
f[i][1]表示i到其父亲节点的距离+其父亲的最长距离(对应第二种情况);
f[i][0]实际上不需要DP,dfs就可求解(本次dfs也求出次长距离);对于f[i][1],我们采取从父节点到子节点的策略。
假设有一L节点,它有n个子节点,我们用Vi来表示。
此时,我们又要分情况来讨论了:
①若Vi不是L的最长路径所经过节点,有f[Vi][1]=dis(Vi,L)+max(f[L][0],f[L][1]);
②若Vi是L的最长路径所经过节点,Vi的最长路径就不能再次被计算,我们就要“退而求其次”,使用第二长的路径Sec_dis,此时有f[Vi][1]=dis(Vi,L)+max(Sec_dis,f[L][1])。
<通过求树的直径解决>
树的直径是什么?就是在一棵树上存在的两点之间的最长距离所经过的路径。例如:
在该图中,树的直径,即距离最长的两点间路径为:7、5、2、1、3、6
在这里我们将用到一个结论:对于任意一个树中的节点来说,距离它最远的点一定是这棵树的直径的两个端点中的一个。【详细证明】
通过这个结论,我们就可以先对两点进行dfs求最远点,确定树的直径的两个端点,然后对树中的点进行dfs求出长度即可。
Code
#include<cstring> #include<cstdio> #include<algorithm> #define MAXN 10010 using namespace std; struct node_a { int head; }V[MAXN]; struct node_b { int v; int w; int next; }E[MAXN]; int top; void init() { memset(V,-1,sizeof(V)); top=0; } void add_edge(int u,int v,int w) { E[top].v=v; E[top].w=w; E[top].next=V[u].head; V[u].head=top++; } int dp[MAXN][3]; void dfs1(int u) { int biggest=0,bigger=0; for(int i=V[u].head;~i;i=E[i].next) { int v=E[i].v; dfs1(v); int tmp=dp[v][0]+E[i].w; if(biggest<=tmp) { bigger=biggest; biggest=tmp; } else if(bigger<tmp) { bigger=tmp; } } dp[u][0]=biggest; dp[u][1]=bigger; } void dfs2(int u) { for(int i=V[u].head;~i;i=E[i].next) { int v=E[i].v; dp[v][2]=max(dp[u][2],dp[v][0]+E[i].w==dp[u][0] ? dp[u][1] : dp[u][0])+E[i].w; dfs2(v); } } int main() {int n; scanf("%d",&n); init(); for(int v=2;v<=n;v++) { int u,w; scanf("%d%d",&u,&w); add_edge(u,v,w); } dfs1(1); dp[1][2]=0; dfs2(1); for(int i=1;i<=n;i++) { printf("%d\\n",max(dp[i][0],dp[i][2])); } return 0; }
[CQBZOJ 1550] 避开怪兽
题目描述
输入
第1行:2个整数N和M(1 ≤ N, M ≤ 500) 接下来N行,每行M个字符,可能是\'.\', \'+\', \'V\', \'J\'等. 数据保证仅包含1个\'V\' 和1个 \'J\' ,至少有1个\'+\'
输出
第1行:1个整数,表示最短曼哈顿距离的最大值
样例数据
样例输入1 | 样例输出1 |
4 4 |
3 |
样例输入2 | 样例输出2 |
4 5 |
0 |
解析
因为要求一条路使得怪兽最近的距离最远。
于是预处理出每一个点与怪兽的最近距离,因为走得越远与怪兽距离的最小值不会变大,具有单调性,然后跑dijsktra就行了。
Code
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<iostream> #include<queue> #define MAXN 250000 #define MAXM #define INF 0x3f3f3f3f typedef long long int LL; using namespace std; struct node { int v; node *next; }*adj[MAXN+10],Edges[MAXN*4],*New=Edges; const int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; void addedges(int u,int v) { node *p=++New; p->v=v; p->next=adj[u]; adj[u]=p; p=++New; p->v=u; p->next=adj[v]; adj[v]=p; } int id[510][510]; int weath[MAXN+10]; struct point { int x,y; point(){} point(int a,int b):x(a),y(b){} }; int N,M; int Sx,Sy,Tx,Ty; queue<point>que1; void bfs() { point now; while(!que1.empty()) { now=que1.front();que1.pop(); int nx,ny; for(int i=0;i<4;++i) { nx=now.x+dir[i][0],ny=now.y+dir[i][1]; if(nx<1||ny<1||nx>M||ny>N) continue; if(weath[id[nx][ny]]>weath[id[now.x][now.y]]+1) { weath[id[nx][ny]]=weath[id[now.x][now.y]]+1; que1.push(point(nx,ny)); } } } } struct data { int pos,w; data(){} data(int a,int b):pos(a),w(b){} bool operator < (const data &a)const { return w<a.w; } }; int dist[MAXN+10]; bool vis[MAXN+10]; priority_queue<data>que2; int dijkstra() { memset(dist,0x3f,sizeof(dist)); while(!que2.empty())que2.pop(); int S=id[Sx][Sy],T=id[Tx][Ty]; dist[S]=weath[S]; que2.push(data(S,0)); data now; while(!que2.empty()) { now=que2.top();que2.pop(); if(vis[now.pos]) continue; else vis[now.pos]=1; if(now.pos==T) return dist[now.pos]; for(node *p=adj[now.pos];p!=NULL;p=p->next) if(dist[p->v]>min(dist[now.pos],weath[p->v])) { dist[p->v]=min(dist[now.pos],weath[p->v]); que2.push(data(p->v,dist[p->v])); } } return -1; } int main() { scanf("%d%d",&M,&N); memset(weath,0x3f,sizeof(weath)); char s[510]; for(int i=1;i<=M;++i) { scanf("%s",s+1); for(int j=1;j<=N;++j) { id[i][j]=(i-1)*N+j; if(s[j]==\'+\') { weath[id[i][j]]=0; que1.push(point(i,j)); } else if(s[j]==\'V\') Sx=i,Sy=j; else if(s[j]==\'J\') Tx=i,Ty=j; } } for(int i=1;i<=M;++i) for(int j=1;j<=N;++j) { if(j!=N) addedges(id[i][j],id[i][j+1]); if(i!=M) addedges(id[i][j],id[i+1][j]); } bfs(); printf("%d\\n",dijkstra()); return 0; }
[BZOJ 1592] 路面修整 (Making the Grade)
题目描述
Farmer John打算好好修一下农场中某条凹凸不平的土路。按奶牛们的要求,修好后的路面高度应当单调上升或单调下降,也就是说,高度上升与高度下降的路段不能同时出现在修好的路中。 整条路被分成了N段,N个整数A_1, ... , A_N (1 <= N <= 2,000)依次描述了每一段路的高度(0 <= A_i <= 1,000,000,000)。Farmer John希望找到一个恰好含N个元素的不上升或不下降序列B_1, ... , B_N,作为修过的路中每个路段的高度。由于将每一段路垫高或挖低一个单位的花费相同,修路的总支出可以表示为: |A_1 - B_1| + |A_2 - B_2| + ... + |A_N - B_N| 请你计算一下,Farmer John在这项工程上的最小支出是多少。Farmer John向你保证,这个支出不会超过2^31-1。
输入
第1行: 输入1个整数:N
第2..N+1行: 第i+1行为1个整数:A_i
输出
第1行: 输出1个正整数,表示FJ把路修成高度不上升或高度不下降的最小花费
样例数据
样例输入 | 样例输出 |
7 |
3 |
解析
把高度进行修改时,一定是把它修改成原数组中的数字是最优的。因为无论是将数字加减多少,总是当它和旁边的数字一样大是最优的。因为这样能刚好满足单调性(两数相等)并且改变的数值最小。有可能前面修改的数字在后面出现要变动的情况,所以它有可能取到原数组中的任何一个数字。然后预处理排序一下得到有序的数组b[]。于是可以写出dp方程:f[i][j]表示前i个数、末尾的数改成了第j大的数的最小 以上是关于[SinGuLaRiTy] 图论题目复习的主要内容,如果未能解决你的问题,请参考以下文章