@CQUPTacm
2017-03-17T12:44:26.000000Z
字数 13552
阅读 1319
题解
A Agri-Net (POJ - 1258)
题意:
有N个农场(3<=N<=100),给出一个邻接矩阵表示农场之间的距离,两个农场之间的双向距离是相同的,距离不超过100000。在两个农场之间修一条双向路的代价等于这两个的距离。问将所有农场联通起来的最小代价是多少。
多组数据。
题解:
把距离作为边的权值,这道题就变成让我们求最小生成树的总权值。
最小生成树一般常用prim和kruskal这两种做法。
prim时间复杂度o(N^2),空间复杂度o(N^2)。
kruskal时间复杂度o(N^2*log(n^2)),空间复杂度o(N^2)。
代码:
prim:
#include<iostream>#include<cstdio>using namespace std;#define INF 666666bool life[105];int val[105];int n,dis[105][105],ans;int main(){int u,v,m,now;val[0]=INF;while(~scanf("%d",&n)){ans=0;for(int i=1;i<=n;i++){life[i]=0;val[i]=INF;for(int j=1;j<=n;j++)scanf("%d",&dis[i][j]);}val[1]=0;m=n;while(m--){now=0;for(int i=1;i<=n;i++){if(!life[i] && val[i]<val[now])now=i;}ans+=val[now];life[now]=1;for(int j=1;j<=n;j++)val[j]=min(val[j],dis[now][j]);}printf("%d\n",ans);}return 0;}
kruskal:
#include<iostream>#include<cstdio>#include<algorithm>using namespace std;struct Edge{int u,v,dis;}e[10005];bool cmp(Edge x1,Edge x2){return x1.dis<x2.dis;}int root[105],nums,n,ans;int findr(int x){if(root[x]==x)return x;elsereturn root[x]=findr(root[x]);}int main(){int u,v;while(~scanf("%d",&n)){ans=0;nums=0;for(int i=1;i<=n;i++){root[i]=i;for(int j=1;j<=n;j++){e[nums].u=i;e[nums].v=j;scanf("%d",&e[nums].dis);nums++;}}sort(e,e+nums,cmp);for(int i=0;i<nums;i++){u=findr(e[i].u);v=findr(e[i].v);if(u!=v){root[u]=v;ans+=e[i].dis;}}printf("%d\n",ans);}return 0;}
B Highways (POJ - 2485)
题意:
有N(3<=N<=500)个村庄,给出一个邻接矩阵表示村庄之间的路的长度,路是双向的,长度在[1,65536]范围内。求要把所有的村庄通过路连起来,最长的路的长度。
多组数据。
题解:
这题要我们做的事其实就是尽量用短的路看能不能联通所有村庄,如果不能连起来就继续用路。其实就是kruskal的做法。
因为kruskal能做,所以prim生成的生成树上最长的边也是答案。
kruskal时间复杂度o(N^2*log(n^2)),空间复杂度o(N^2)。
prim时间复杂度o(N^2),空间复杂度o(N^2)。
代码:
kruskal:
#include<iostream>#include<cstdio>#include<algorithm>using namespace std;struct Edge{int u,v,dis;}e[250005];bool cmp(Edge x1,Edge x2){return x1.dis<x2.dis;}int root[505],nums,n,ans;int findr(int x){if(root[x]==x)return x;elsereturn root[x]=findr(root[x]);}int main(){int u,v,T;scanf("%d",&T);while(T--){scanf("%d",&n);ans=0;nums=0;for(int i=1;i<=n;i++){root[i]=i;for(int j=1;j<=n;j++){e[nums].u=i;e[nums].v=j;scanf("%d",&e[nums].dis);nums++;}}sort(e,e+nums,cmp);for(int i=0;i<nums;i++){u=findr(e[i].u);v=findr(e[i].v);if(u!=v){root[u]=v;ans=max(ans,e[i].dis);}}printf("%d\n",ans);}return 0;}
prim:
#include<iostream>#include<cstdio>using namespace std;#define INF 666666bool life[505];int val[505];int n,dis[505][505],ans;int main(){int u,v,m,now,T;val[0]=INF;scanf("%d",&T);while(T--){scanf("%d",&n);ans=0;for(int i=1;i<=n;i++){life[i]=0;val[i]=INF;for(int j=1;j<=n;j++)scanf("%d",&dis[i][j]);}val[1]=0;m=n;while(m--){now=0;for(int i=1;i<=n;i++){if(!life[i] && val[i]<val[now])now=i;}ans=max(ans,val[now]);life[now]=1;for(int j=1;j<=n;j++)val[j]=min(val[j],dis[now][j]);}printf("%d\n",ans);}return 0;}
C 继续畅通工程 (HDU - 1879)
题意:
中文题。。。
题解:
如果不考虑路的修建状况,这题就是求最小生成树的总权值。
如果路已经修建完成,那么相当于这条路的代价为0。将所有建好的路的权值改为0,跑最小生成树求总权值即可。
代码:
prim:
#include<iostream>#include<cstdio>using namespace std;#define INF 666666bool life[105];int val[105];int n,dis[105][105],ans;int main(){int u,v,m,now,flag;val[0]=INF;while(scanf("%d",&n),n){ans=0;for(int i=1;i<=n;i++){life[i]=0;dis[i][i]=0;val[i]=INF;}m=n*(n-1)/2;for(int i=0;i<m;i++){scanf("%d%d%d%d",&u,&v,&now,&flag);if(flag)dis[u][v]=dis[v][u]=0;elsedis[u][v]=dis[v][u]=now;}val[1]=0;m=n;while(m--){now=0;for(int i=1;i<=n;i++){if(!life[i] && val[i]<val[now])now=i;}ans+=val[now];life[now]=1;for(int j=1;j<=n;j++)val[j]=min(val[j],dis[now][j]);}printf("%d\n",ans);}return 0;}
kruskal:
#include<iostream>#include<cstdio>#include<algorithm>using namespace std;struct Edge{int u,v,dis;}e[10005];bool cmp(Edge x1,Edge x2){return x1.dis<x2.dis;}int root[105],nums,n,ans;int findr(int x){if(root[x]==x)return x;elsereturn root[x]=findr(root[x]);}int main(){int u,v,now;while(scanf("%d",&n),n){ans=0;nums=n*(n-1)/2;for(int i=1;i<=n;i++)root[i]=i;for(int i=0;i<nums;i++){scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].dis,&now);if(now)e[i].dis=0;}sort(e,e+nums,cmp);for(int i=0;i<nums;i++){u=findr(e[i].u);v=findr(e[i].v);if(u!=v){root[u]=v;ans+=e[i].dis;}}printf("%d\n",ans);}return 0;}
D Genealogical tree (POJ - 2367)
题意:
有N(1<=N<=100)个火星人,给出他们每个人的后代信息,求一种发言顺序,在每个人发一次言的情况下保证每个人发言的时候自己的后代都还没发言。题目保证至少有一种合法的发言顺序。
题解:
当一个人的祖先都发过言了,这个人就可以发言了,同时他的所有后代的未发言祖先都要扣掉他。每次找出没有未发言祖先的人让他发言即可。保证祖先在后代之前,这实际上就是一个求拓扑序列的过程。
时间复杂度o(N^2),空间复杂度o(N^2)。
代码:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666666int fa[105],n;queue <int> son[105],q;int main(){int now,temp;bool flag=0;scanf("%d",&n);for(int i=1;i<=n;i++){while(scanf("%d",&now),now){son[i].push(now);fa[now]++;}}for(int i=1;i<=n;i++){if(fa[i]==0)q.push(i);}while(!q.empty()){now=q.front();q.pop();if(!flag)flag=1;elseprintf(" ");printf("%d",now);while(!son[now].empty()){temp=son[now].front();son[now].pop();fa[temp]--;if(fa[temp]==0)q.push(temp);}}printf("\n");return 0;}
E 排名表 (UESTC - 1150)
题意:
中文题。
题解:
忽略让排名小的排在前面这个规则,这题就是求拓扑序列。
判断拓扑序列是否存在的方法,就是判断有没有环。如果有环的话,环上的点肯定都不能进队列,所以我们只需要看一共有多少个点进过队列就行了。
考虑到要让排名小的排在前面,我们可以反过来想,让序号小的尽可能排后面,最后输出的时候把名次反过来看就行了。为什么要反过来想呢?给出一个例子大家自己思考:3个人,3在1前面,三个人的顺序应该是3、1、2,但是如果让当前序号小的先排,就会变成2、3、1。所以我们把拓扑的关系反过来建,然后用一个大序号排前面的优先队列来代替队列,最后输出的时候把排名反过来看就行了。
ps:注意初始化。
时间复杂度o(T*n^2*log(n^2)),空间复杂度o(n^2)。
代码:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666666int fa[205],n,ans[205];queue <int> son[205];priority_queue <int> q;int flag;int main(){int m,now,temp,T,u,v;scanf("%d",&T);while(T--){flag=0;scanf("%d%d",&n,&m);while(m--){scanf("%d%d",&u,&v);fa[u]++;son[v].push(u);}for(int i=1;i<=n;i++){if(fa[i]==0)q.push(i);}while(!q.empty()){now=q.top();q.pop();flag++;ans[now]=flag;while(!son[now].empty()){temp=son[now].front();son[now].pop();fa[temp]--;if(fa[temp]==0)q.push(temp);}}if(flag==n){for(int i=1;i<=n;i++){if(i>1)printf(" ");printf("%d",n+1-ans[i]);}printf("\n");}else{for(int i=1;i<=n;i++){fa[i]=0;while(!son[i].empty()){son[i].pop();}}printf("-1\n");}}return 0;}
F 畅通工程续 (HDU - 1874)
题意:
中文题。
题解:
题目求的是S和T之间的最短路,可以转化成S到任意点的最短路,然后看S到T的距离。
单源最短路一般有Dijkstra(优先队列优化)和spfa(队列优化的Bellman-Ford)两种算法。
Dijkstra的时间复杂度为o(M*log(M)),空间复杂度为o(M)。
spfa的时间复杂度为o(k*M),空间复杂度为o(M)。
代码:
Dijkstra:
#include<iostream>#include<cstdio>#include<queue>using namespace std;struct Edge{int to,dis;friend bool operator <(Edge x1,Edge x2){return x1.dis>x2.dis;}}temp;int n,dis[205];bool life[205];priority_queue <Edge> pq;vector <Edge> vec[205];int main(){int m,u,v,s,t;while(~scanf("%d%d",&n,&m)){for(int i=0;i<n;i++){vec[i].clear();life[i]=0;}while(m--){scanf("%d%d%d",&u,&v,&temp.dis);temp.to=v;vec[u].push_back(temp);temp.to=u;vec[v].push_back(temp);}scanf("%d%d",&s,&t);temp.to=s;temp.dis=0;pq.push(temp);while(!pq.empty()){temp=pq.top();pq.pop();if(!life[temp.to]){u=temp.to;life[u]=1;dis[u]=temp.dis;for(int i=0;i<vec[u].size();i++){if(!life[vec[u][i].to]){temp.to=vec[u][i].to;temp.dis=vec[u][i].dis+dis[u];pq.push(temp);}}}}if(!life[t])printf("-1\n");elseprintf("%d\n",dis[t]);}return 0;}
spfa:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 20000007struct Edge{int to,dis;}temp;int n,dis[205];bool life[205];queue <int> q;vector <Edge> vec[205];int main(){int m,u,v,s,t;while(~scanf("%d%d",&n,&m)){for(int i=0;i<n;i++){vec[i].clear();life[i]=0;dis[i]=INF;}while(m--){scanf("%d%d%d",&u,&v,&temp.dis);temp.to=v;vec[u].push_back(temp);temp.to=u;vec[v].push_back(temp);}scanf("%d%d",&s,&t);dis[s]=0;q.push(s);life[s]=1;while(!q.empty()){u=q.front();q.pop();life[u]=0;for(int i=0;i<vec[u].size();i++){if(vec[u][i].dis+dis[u]<dis[vec[u][i].to]){dis[vec[u][i].to]=vec[u][i].dis+dis[u];if(!life[vec[u][i].to]){life[vec[u][i].to]=1;q.push(vec[u][i].to);}}}}if(dis[t]==INF)printf("-1\n");elseprintf("%d\n",dis[t]);}return 0;}
G Tram (POJ - 1847)
题意:
有N(2<=N<=100)个岔路口,第i个岔路口链接Ki(0<=Ki<=N-1)条轨道通向不同的岔路口,若Ki不为0,则第i个岔路口一开始的方向为它连接的第一条轨道,如果想要从一个岔路口开向他当前没有连接的那条轨道,则需要更改1次这个岔路口的方向。问从A号岔路口到B号岔路口,最少需要改变多少次岔路口的方向。
题解:
从点i到i默认通往的那个岔路口去,需要0次转向,到i默认之外的其他连接的岔路口去,需要1次转向。所以可以把每个岔路口到默认岔路口的那条边的权值定为0,剩下的边权值定为1,问题就转化成了从A到B的最短距离。
ps:因为改变最多次的情况是每个岔路口都经过而且每次都改变方向,所以INF只要大于N就行了。
Dijkstra的时间复杂度为o(sum(K)*log(sum(K))),空间复杂度为o(sum(K))。
spfa的时间复杂度为o(k*sum(K)),空间复杂度为o(sum(K))。
代码:
Dijkstra:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666struct Edge{int to,dis;friend bool operator <(Edge x1,Edge x2){return x1.dis>x2.dis;}}temp;int n,dis[105];bool life[105];priority_queue <Edge> pq;vector <Edge> vec[105];int main(){int u,v,s,t;scanf("%d%d%d",&n,&s,&t);for(int i=1;i<=n;i++){vec[i].clear();life[i]=0;dis[i]=INF;scanf("%d",&u);for(int j=0;j<u;j++){scanf("%d",&v);if(!j)temp.dis=0;elsetemp.dis=1;temp.to=v;vec[i].push_back(temp);}}temp.to=s;temp.dis=0;pq.push(temp);while(!pq.empty()){temp=pq.top();pq.pop();if(!life[temp.to]){u=temp.to;life[u]=1;dis[u]=temp.dis;for(int i=0;i<vec[u].size();i++){if(!life[vec[u][i].to]){temp.to=vec[u][i].to;temp.dis=vec[u][i].dis+dis[u];pq.push(temp);}}}}if(dis[t]==INF)printf("-1\n");elseprintf("%d\n",dis[t]);return 0;}
spfa:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666struct Edge{int to,dis;}temp;int n,dis[105];bool life[105];queue <int> q;vector <Edge> vec[105];int main(){int u,v,s,t;scanf("%d%d%d",&n,&s,&t);for(int i=1;i<=n;i++){vec[i].clear();life[i]=0;dis[i]=INF;scanf("%d",&u);for(int j=0;j<u;j++){scanf("%d",&v);if(!j)temp.dis=0;elsetemp.dis=1;temp.to=v;vec[i].push_back(temp);}}dis[s]=0;q.push(s);life[s]=1;while(!q.empty()){u=q.front();q.pop();life[u]=0;for(int i=0;i<vec[u].size();i++){if(vec[u][i].dis+dis[u]<dis[vec[u][i].to]){dis[vec[u][i].to]=vec[u][i].dis+dis[u];if(!life[vec[u][i].to]){life[vec[u][i].to]=1;q.push(vec[u][i].to);}}}}if(dis[t]==INF)printf("-1\n");elseprintf("%d\n",dis[t]);return 0;}
H 一个人的旅行 (HDU - 2066)
题意:
中文题。
题解:
因为点的标号范围是[1,1000],所以可以把草儿家标为0号点,然后把所有和草儿家相邻的S个点和0号点连一条距离为0的边,然后把目的地设置为1001号点,然后把想去的D个点和1001号点连一条距离为0的边,然后求0到1001的最短路就行了。
Dijkstra的时间复杂度为o(T*log(T)),空间复杂度为o(T)。
spfa的时间复杂度为o(k*T),空间复杂度为o(T)。
代码:
Dijkstra:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 2000000006struct Edge{int to,dis;friend bool operator <(Edge x1,Edge x2){return x1.dis>x2.dis;}}temp;int n;int dis[1005];bool life[1005];priority_queue <Edge> pq;vector <Edge> vec[1005];int main(){int T,S,D;int u,v;while(~scanf("%d%d%d",&T,&S,&D)){for(int i=0;i<=1001;i++){vec[i].clear();life[i]=0;dis[i]=INF;}while(T--){scanf("%d%d%d",&u,&v,&temp.dis);temp.to=v;vec[u].push_back(temp);temp.to=u;vec[v].push_back(temp);}temp.dis=0;while(S--){scanf("%d",&temp.to);vec[0].push_back(temp);}temp.to=1001;while(D--){scanf("%d",&u);vec[u].push_back(temp);}temp.to=0;temp.dis=0;pq.push(temp);while(!pq.empty()){temp=pq.top();pq.pop();if(!life[temp.to]){u=temp.to;life[u]=1;dis[u]=temp.dis;for(int i=0;i<vec[u].size();i++){if(!life[vec[u][i].to]){temp.to=vec[u][i].to;temp.dis=vec[u][i].dis+dis[u];pq.push(temp);}}}}if(dis[1001]==INF)printf("-1\n");elseprintf("%d\n",dis[1001]);}return 0;}
spfa:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666struct Edge{int to,dis;}temp;int n,dis[1005];bool life[1005];queue <int> q;vector <Edge> vec[1005];int main(){int T,S,D;int u,v;while(~scanf("%d%d%d",&T,&S,&D)){for(int i=0;i<=1001;i++){vec[i].clear();life[i]=0;dis[i]=INF;}while(T--){scanf("%d%d%d",&u,&v,&temp.dis);temp.to=v;vec[u].push_back(temp);temp.to=u;vec[v].push_back(temp);}temp.dis=0;while(S--){scanf("%d",&temp.to);vec[0].push_back(temp);}temp.to=1001;while(D--){scanf("%d",&u);vec[u].push_back(temp);}dis[0]=0;q.push(0);life[0]=1;while(!q.empty()){u=q.front();q.pop();life[u]=0;for(int i=0;i<vec[u].size();i++){if(vec[u][i].dis+dis[u]<dis[vec[u][i].to]){dis[vec[u][i].to]=vec[u][i].dis+dis[u];if(!life[vec[u][i].to]){life[vec[u][i].to]=1;q.push(vec[u][i].to);}}}}if(dis[1001]==INF)printf("-1\n");elseprintf("%d\n",dis[1001]);}return 0;}
I 六度分离 (HDU - 1869)
题意:
中文题。
题解:
相互认识的人之间连一条长度为1的边,任意两个人之间最多隔着6个人也就等同于任意两个人的距离不超过7。求一遍任意两个点的最短距离,然后看有没有超过7的就行了。
多源最短路一般用Floyd算法求。
时间复杂度o(N^3),空间复杂度o(N^2)。
代码:
#include<iostream>#include<cstdio>#include<queue>using namespace std;#define INF 666int dis[105][105],n,m;int main(){int ans;int u,v;while(~scanf("%d%d",&n,&m)){ans=7;for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(i==j)dis[i][j]=0;elsedis[i][j]=INF;}}while(m--){scanf("%d%d",&u,&v);dis[u][v]=dis[v][u]=1;}for(int k=0;k<n;k++)for(int i=0;i<n;i++)for(int j=0;j<n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);for(int i=0;i<n;i++)for(int j=0;j<n;j++)ans=max(ans,dis[i][j]);if(ans==7)printf("Yes\n");elseprintf("No\n");}return 0;}
J John's trip (POJ - 1041)
题意:
有最多m(m<=44)个点,最多n(n<=1995)条无向边,问是否存在一条路线,从第一条边所连接的序号较小点出发,经过所有的边一次,回到出发点。如果存在,输出经过的边字典序最小的一个方案。
多组数据。
题解:
无向图有欧拉路要满足两个条件:(1)每个点的度数都是偶数(2)任意两个有路连接的点都是连通的
当存在欧拉路的时候,对于当前点,优先选择还没走的序号较小的边,然后先把这条边连接的另一个点作为起点继续走,然后再回头记录这条边,最后逆序输出。为什么要这样改变顺序呢?给出一组数据供思考,格式为 边序号:连接的第一个点&连接的第二个点
1:1&2
2:2&3
3:1&3
4:2&4
5:2&4
ps:输入比较恶心,要处理一些细节。
时间复杂度o(n),空间复杂度o(n)。
代码:
#include<iostream>#include<cstdio>#include<queue>#include<cstring>#include<algorithm>using namespace std;struct Edge{int to,no;friend bool operator <(Edge x1,Edge x2){return x1.no>x2.no;}}temp;vector <Edge> vec[55];int nums,l[55],cnt,ans[2005];bool life[2005],used[55];queue <int> q;void dfs(int s){for(;l[s]<vec[s].size();l[s]++){if(life[vec[s][l[s]].no]){int temp=vec[s][l[s]].no;life[vec[s][l[s]].no]=0;dfs(vec[s][l[s]].to);ans[cnt++]=temp;}}}int main(){bool flag;int u,v,d,s;while(1){flag=0;nums=0;memset(life,0,sizeof(life));memset(used,0,sizeof(used));memset(l,0,sizeof(l));for(int i=1;i<=50;i++)vec[i].clear();while(1){scanf("%d%d",&u,&v);if(!u && !v)break;else{used[u]=used[v]=1;if(!nums)s=min(u,v);nums++;scanf("%d",&temp.no);temp.to=u;vec[v].push_back(temp);temp.to=v;vec[u].push_back(temp);life[temp.no]=1;}}if(!nums)break;else{q.push(s);used[s]=0;while(!q.empty()){u=q.front();q.pop();for(int i=0;i<vec[u].size();i++){if(used[vec[u][i].to]){q.push(vec[u][i].to);used[vec[u][i].to]=0;}}}for(int i=1;i<=50;i++){if(used[i] || vec[i].size()%2)flag=1;elsesort(vec[i].begin(),vec[i].end());}if(flag)printf("Round trip does not exist.\n");else{cnt=0;dfs(s);while(nums--){printf("%d",ans[nums]);if(nums)printf(" ");elseprintf("\n");}}}}return 0;}