P1119 灾后重建
题目背景
B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
题目描述
给出 B 地区的村庄数 N,村庄编号从 0 到 N−1,和所有 M 条公路的长度,公路是双向的。并给出第 i 个村庄重建完成的时间 ti ,你可以认为是同时开始重建并在第 ti 天重建完成,并且在当天即可通车。若 ti 为 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q 个询问 (x,y,t),对于每个询问你要回答在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未重建完成,则需要输出 −1。
输入格式
第一行包含两个正整数 N,M,表示了村庄的数目与公路的数量。
第二行包含 N 个非负整数 t0,t1,⋯,tN−1 ,表示了每个村庄重建完成的时间,数据保证了 t0≤t1≤⋯≤tN−1。
接下来 M 行,每行 3 个非负整数 i,j,w,w 为不超过 10000 的正整数,表示了有一条连接村庄 i 与村庄 j 的道路,长度为 w,保证 i≠j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是 M+3 行包含一个正整数 Q,表示 Q 个询问。
接下来 Q 行,每行 3 个非负整数 x,y,t,询问在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少,数据保证了 t 是不下降的。
输出格式
共 Q 行,对每一个询问 (x,y,t) 输出对应的答案,即在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果在第 t 天无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未修复完成,则输出 −1。
#include <bits/stdc++.h> #define MAX_ 0x7f7f7f #define N 205 using namespace std; int n,m,q; int day[N]; int e[N][N]; //储存边 void update(int k) { for(int i=0;i<n;i++) { for(int j=0;j<n;j++) { if(e[i][j]>e[i][k]+e[k][j]) e[i][j]=e[i][k]+e[k][j]; } } } int main() { cin>>n>>m; for(int i=0;i<n;i++) cin>>day[i]; for(int i=0;i<n;i++) for(int j=0;j<n;j++) { if(j==i) e[i][j]=0; else e[i][j]=MAX_; } for(int i=1;i<=m;i++) { int x,y,z; cin>>x>>y>>z; e[x][y]=e[y][x]=z; } cin>>q; int now=0; for(int i=1;i<=q;i++) { int a,b,c; cin>>a>>b>>c; while(day[now]<=c&&now<n) { update(now); now++; } if(day[a]>c||day[b]>c) cout<<-1<<endl; else { if(e[a][b]==MAX_) cout<<-1<<endl; else cout<<e[a][b]<<endl; } } return 0; }
一开始想的是floyd的算法,但是想试试dijstra,写法TLE了。。。
用Floyd的做法,要注意的是e[i][j]当i=j的时候,要初始化成0,其他情况初始化成MAX_。还要注意的是,由于t是不下降的,所以对于每个询问,以now为中转接点已经更新过的,不要再重新更新,不然就会TLE。
P8604 [蓝桥杯 2013 国 C] 危险系数
题目背景
抗日战争时期,冀中平原的地道战曾发挥重要作用。
题目描述
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数 DF(x,y):对于两个站点 x 和 y(x≠y), 如果能找到一个站点 z,当 z 被敌人破坏后,x 和 y 不连通,那么我们称 z 为关于 x,y 的关键点。相应的,对于任意一对站点 x 和 y,危险系数 DF(x,y) 就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
输入格式
输入数据第一行包含 2 个整数 n(2≤n≤1000),m(0≤m≤2000),分别代表站点数,通道数。
接下来 m 行,每行两个整数 u,v(1≤u,v≤n,u≠v) 代表一条通道。
最后 1 行,两个数 u,v,代表询问两点之间的危险系数 DF(u,v)。
输出格式
一个整数,如果询问的两点不连通则输出 −1。
#include <bits/stdc++.h> #define MAX_ 0x7f7f7f #define N 100005 using namespace std; struct edge { int to; int nxt; }e[N*4]; int n,m,x,y; int cntt=0,ans=0,res=0; int head[N],vis[N],cnt[N]; stack<int> s; void add(int u,int v) { e[++cntt].to=v; e[cntt].nxt=head[u]; head[u]=cntt; } void DFS(int ss) { if(ss==y) { ans++; for(int i=1;i<=n;i++) { if(vis[i]) cnt[i]++; } return; } while(!s.empty()) { int tmp=s.top(); s.pop(); vis[tmp]=1; for(int i=head[tmp];i;i=e[i].nxt) { int temp=e[i].to; if(vis[temp]) continue; vis[temp]=1; s.push(temp); DFS(temp); } vis[tmp]=0; } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; add(a,b); add(b,a); } cin>>x>>y; s.push(x); DFS(x); for(int i=1;i<=n;i++) { if(cnt[i]==ans) res++; } if(ans) cout<<res-2; else cout<<-1; return 0; }
这道题我一开始的想法是,求最短路径,最短路径-1即为关键点的个数,美美40分。。。
后来我才发现,关键点的判定标准是:cnt[i]的数量是否与可行路径数相等。cnt[i]的计算方式为:
每产生一条新的路径,这条路径上vis为1的点i的cnt[i]+1
P1330 封锁阳光大学
题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。
阳光大学的校园是一张由 n 个点构成的无向图,n 个点之间由 m 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。
询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。
输入格式
第一行两个正整数,表示节点数和边数。 接下来 m 行,每行两个整数 u,v,表示点 u 到点 v 之间有道路相连。
输出格式
仅一行如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。
#include <bits/stdc++.h> #define MAX_ 0x7f7f7f #define N 100005 using namespace std; struct edge { int to; int nxt; int num; }e[N*4]; int n,m; //n个村庄,m条路 int cnt=0; int ans; int head[N],vis[N],sum[3]; queue<int> q; void add(int u,int v) { e[++cnt].to=v; e[cnt].nxt=head[u]; head[u]=cnt; } bool BFS(int s) { vis[s]=1; sum[1]=1,sum[2]=0; q.push(s); while(!q.empty()) { int tmp=q.front(); q.pop(); for(int i=head[tmp];i;i=e[i].nxt) { if(vis[e[i].to]==vis[tmp]) return 0; else if(vis[e[i].to]==vis[tmp]%2+1) continue; else { vis[e[i].to]=vis[tmp]%2+1; sum[vis[tmp]%2+1]++; q.push(e[i].to); } } } return 1; } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; add(a,b); add(b,a); } for(int i=1;i<=n;i++) { if(!vis[i]) { if(!BFS(i)) { cout<<"Impossible"; return 0; } else ans+=min(sum[1],sum[2]); } } cout<<ans; return 0; }
这道题一开始我想的是利用优先队列,先把所有的点推入队列。从临边数最多的点开始,只要是与它相邻的就标记,同时ans+1,记录总边数的res再加上它有多少条相邻的边。优先队列里遍历过的点就不再遍历,最后看一下res的值和m的值是否相等,但是喜提40分。。。
无奈看题解吧,题解的思路是用两种颜色对相邻的两个点进行标记,同时利用广搜,没有被染过色的就把它染成与上一个点不一样的颜色,推入队列。对于染过色的,如果这个点染的是与上一个点不同的颜色就continue,否则直接return 0.
P3916 图的遍历
题目描述
给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点。
输入格式
第 1 行 2 个整数 N,M,表示点数和边数。
接下来 M 行,每行 2 个整数 Ui ,Vi,表示边 (Ui,Vi)。点用 1,2,…,N 编号。
输出格式
一行 N 个整数 A(1),A(2),…,A(N)。
#include <bits/stdc++.h> #define MAX_ 0x7f7f7f #define N 100005 using namespace std; struct edge { int to; int nxt; }e[N]; int n,m; //n个村庄,m条路 int cnt=0; int head[N],vis[N],ans[N]; stack<int> qq; void add(int u,int v) { e[++cnt].to=v; e[cnt].nxt=head[u]; head[u]=cnt; } void DFS(int s) { while(!qq.empty()) { int tmp=qq.top(); qq.pop(); for(int i=head[tmp];i;i=e[i].nxt) { int temp=e[i].to; if(ans[temp]<ans[tmp]) ans[temp]=ans[tmp]; if(vis[temp]) continue; if(!vis[temp]) { vis[temp]=1; qq.push(temp); DFS(temp); } } } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; add(b,a); } for(int i=1;i<=n;i++) ans[i]=i; for(int i=n;i>=1;i--) { if(!vis[i]) { qq.push(i); vis[i]=1; DFS(i); } } for(int i=1;i<=n;i++) { cout<<ans[i]<<" "; } return 0; }
这道题一开始的时候我是正着存图的,设置了如果s=n或者e[head[s]].nxt=0的时候回溯,顺便更新一下ans[tmp]的值,然后。。。WA了5个点。。。我又去研究了一下题解,发现如果反着存图,并且由大到小遍历,那么小的就一定可以更新到大的,并且不用设置递归出口。
P3367 【模板】并查集
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。
接下来 M 行,每行包含三个整数 Zi,Xi,Yi。当 Zi=1 时,将 Xi 与 Yi 所在的集合合并。
当Zi=2 时,输出 Xi与 Yi是否在同一集合内,是的输出 Y ;否则输出 N 。
输出格式
对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。
#include <bits/stdc++.h> #define MAXN 10010 using namespace std; int n,m; int pa[MAXN]; int find(int x) { if(pa[x]==x) return x; return pa[x]=find(pa[x]); } int main() { cin>>n>>m; for(int i=1;i<=n;i++) pa[i]=i; for(int i=1;i<=m;i++) { int a,b,c; cin>>a>>b>>c; if(a==1) { int tmp=find(c); pa[find(b)]=tmp; } else { int res1=find(b); int res2=find(c); if(res1==res2) cout<<"Y"<<endl; else cout<<"N"<<endl; } } return 0; }