https://vjudge.net/contest/312213#overview password zut
A:http://poj.org/problem?id=2367
给邻接矩阵,跑出任一拓扑序
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e3+5;
vector<int>g[MX];int in[MX];
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
int x;while(1){cin>>x;if(x==0)break;g[i].push_back(x);in[x]++;}
}
queue<int>q;
for(int i=1;i<=n;i++){
if(in[i]==0)q.push(i);
}
while(!q.empty()){
int now=q.front();q.pop();cout<<now<<' ';
for(int i=0;i<(int)g[now].size();i++){
in[g[now][i]]--;
if(in[g[now][i]]==0)q.push(g[now][i]);
}
}
return 0;
}
B:http://acm.hdu.edu.cn/showproblem.php?pid=1285
题意:
给n个队伍,有m个比较,每个比较包括u和v,代表u排名在v之前,求原始的排名,此排名不唯一,输出字典序最小。
思路:
对于每个比较,建立一条u指向v的边,然后拓扑序就是排名
要求字典序,那么将队列换成优先队列,把入度为0的全部丢进优先队列,跑拓扑。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
vector<int>g[MX];int in[MX];
int main(){
int n,m;
while(cin>>n>>m){
for(int i=1;i<=n;i++)in[i]=0,g[i].clear();
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
g[u].push_back(v);in[v]++;
}
priority_queue<int,vector<int>,greater<int> >q;
vector<int>ans;
for(int i=1;i<=n;i++){
if(in[i]==0)q.push(i);
}
while(!q.empty()){
auto now=q.top();q.pop();
ans.push_back(now);
for(auto i:g[now]){
in[i]--;
if(in[i]==0)q.push(i);
}
}
for(int i=0;i<(int)ans.size()-1;i++)cout<<ans[i]<<' ';
cout<<ans[ans.size()-1];
puts("");
}
return 0;
}
C:http://acm.hdu.edu.cn/showproblem.php?pid=3342 //拓扑排序判环
题意:
给一个有向图,问是否有环。
思路:
跑拓扑之后,看看是否还有剩余的点。(剩余的点入度不为0)
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
vector<int>g[MX];int in[MX];
int main(){
int n,m;
while(cin>>n>>m){
if(n==0)break;
for(int i=0;i<=n;i++)in[i]=0,g[i].clear();
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
g[u].push_back(v);in[v]++;
}
queue<int>q;
for(int i=0;i<=n;i++){
if(in[i]==0)q.push(i);
}
while(!q.empty()){
auto now=q.front();q.pop();n--;
for(auto i:g[now]){
in[i]--;
if(in[i]==0)q.push(i);
}
}
(n==-1)?puts("YES"):puts("NO");
}
return 0;
}
D:https://codeforces.com/problemset/problem/1131/D //并查集缩点+拓扑排序
题意:
未知a数组和b数组,长度是m和n,给一个表示大小关系的矩阵 ,xij表示ai和bj的大小关系,求出每个元素最小的a和b(最小从1开始)。
思路:
等于关系的可以缩成一个点,然后偏序关系转换成有向边,生成的图可能会是森林,不是一棵树。
在所有缩点上进行拓扑排序,注意此拓扑序不是普通的拓扑序,有点像逆bfs,也就是不同的点的拓扑序有可能是相同的值。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e3+5;
vector<int>g[MX];int in[MX];int fa[MX];
char mp[MX][MX];int ans[MX];
int found(int x){
if(fa[x]==x)return x;
return fa[x]=found(fa[x]);
}
int main(){
int n,m;cin>>n>>m;
for(int i=0;i<n+m;i++)fa[i]=i;
for(int i=0;i<n;i++)cin>>mp[i];
for(int i=0;i<n;i++){//=的缩点
for(int j=0;j<m;j++){
if(mp[i][j]=='='){
int fi=found(i),fj=found(j+n);
if(fi!=fj)fa[fj]=fa[fi];
}
}
}
for(int i=0;i<n;i++){//其他的再缩点上连边
for(int j=0;j<m;j++){
int fi=found(i),fj=found(j+n);
if(mp[i][j]=='<'){
g[fi].push_back(fj);in[fj]++;
}
if(mp[i][j]=='>'){
g[fj].push_back(fi);in[fi]++;
}
}
}
set<int>ss;//去重
for(int i=0;i<n+m;i++){
int fi=found(i);
if(in[fi]==0)ss.insert(fi);
}
queue<pair<int,int> >q;//拓扑标记
for(auto i:ss)q.push(MP(i,0)),ans[i]=0;
while(!q.empty()){
auto now=q.front();q.pop();
ans[now.FI]=now.SE+1;
for(auto i:g[now.FI]){
in[i]--;
if(in[i]==0)q.push(MP(i,now.SE+1));
}
}
for(int i=0;i<n+m;i++)if(ans[found(i)]==0){puts("No");return 0;}
puts("Yes");
for(int i=0;i<n;i++) cout<<ans[found(i)]<<' ';
cout<<endl;
for(int i=n;i<m+n;i++)cout<<ans[found(i)]<<' ';
return 0;
}
E:https://blog.csdn.net/qq_41730604/article/details/96453763 //二分+拓扑排序
F:http://acm.hdu.edu.cn/showproblem.php?pid=1102
给邻接矩阵,求最小生成树的费用。克鲁斯卡尔
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX],mp[MX][MX];
vector<no>e;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
int main(){
int n;while(cin>>n){
for(int i=1;i<=n;i++)fa[i]=i;
e.clear();
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&mp[i][j]);
int q;cin>>q;while(q--){
int uu,vv;scanf("%d%d",&uu,&vv);
mp[uu][vv]=-1;mp[vv][uu]=-1;
int fu=found(uu),fv=found(vv);
if(fu!=fv){fa[fu]=fa[fv];}
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(mp[i][j]!=-1)
e.push_back(no{i,j,mp[i][j]});
sort(e.begin(),e.end());
int ans=0;
for(auto i:e){
int fu=found(i.u),fv=found(i.v);
if(fu!=fv){fa[fu]=fa[fv];ans+=i.c;}
}
printf("%d\n",ans);
}
return 0;
}
G:http://poj.org/problem?id=2377
题意:求最大生成树
思路:将每条边的wi变成-wi,跑克鲁斯卡尔
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <climits>
#include <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX],mp[MX][MX];
vector<no>e;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
int main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
e.push_back(no{u,v,-w});
}
sort(e.begin(),e.end());
int ans=0;
for(int i=0;i<(int)e.size();i++){
int fu=found(e[i].u),fv=found(e[i].v);
if(fu!=fv){fa[fu]=fa[fv];ans+=e[i].c;}
}
for(int i=2;i<=n;i++)if(found(i)!=found(i-1)){puts("-1");return 0;}
printf("%d\n",-ans);
return 0;
}
H:http://poj.org/problem?id=2395
题意:求最小生成树(此费用不是总和,是生成树的权值最大的边的权值)
思路1:二分这个最大权值,然后看小于等于这个权值的边能不能组成联通图。
思路2:其实不用二分。。。。克鲁斯卡尔跑的时候保证是权值最小的。
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <climits>
#include <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX];
vector<no>e;int n,m;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
bool check(int x){
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=0;i<(int)e.size();i++){
if(e[i].c>x)continue;
int fx=found(e[i].u),fy=found(e[i].v);
if(fx!=fy) fa[fx]=fa[fy];
}
for(int i=2;i<=n;i++)if(found(i)!=found(i-1))return 0;
return 1;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);e.push_back(no{u,v,w});
}
sort(e.begin(),e.end());
int l=1,r=1e9,ans;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans;
return 0;
}
I:https://codeforces.com/problemset/problem/1095/F
题意:
给顶点数n和m,然后m条信息,每条信息包括,ui,vi,wi,表现ui和vi直接添加一条无向边需要花费wi。另外的:任意ui和vi可以添加一条边,代价为a[ui]+a[vi]。求最小生成树。
思路:
假设把后者加边方式的n*(n-1)/2条边算入,克鲁斯卡尔的过程中,只会轮到a[i]最小的边和其他的边连接,其他的轮不到。
那么将前者m条边 和 1连其他点的n-1条边 跑克鲁斯卡尔即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e5+5;
LL a[MX];
int fa[MX];
struct no{int u,v;LL w;};
bool operator<(const no &x,const no &y){return x.w<y.w;}
vector<no>e;
int found(int x){if(fa[x]==x)return x;return fa[x]=found(fa[x]);}
int main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
LL mi=LLONG_MAX;int u;
for(int i=1;i<=n;i++){
scanf("%I64d",&a[i]);if(a[i]<mi)mi=a[i],u=i;
}
for(int i=1;i<=m;i++){
int u,v;LL w;
scanf("%d%d%I64d",&u,&v,&w);
e.push_back(no{u,v,w});
}
for(int i=1;i<=n;i++){
if(i==u)continue;
e.push_back(no{i,u,a[i]+a[u]});
}
sort(e.begin(),e.end());
LL ans=0;
for(auto i:e){
int fu=found(i.u),fv=found(i.v);
if(fu!=fv)ans+=i.w,fa[fu]=fa[fv];
}
cout<<ans;
return 0;
}
J:https://codeforces.com/problemset/problem/1108/F
题意:
给一个联通simpy图,此时最小生成树可能不唯一,你可以给每条边加权值,使得最小生成树唯一,求加的权值的和最小是多少。
思路:
考虑克鲁斯卡尔的过程,处理同一wi的边的时候,有可能有多种选择使得处理完这个wi后效果相同。那么将这些多余的wi加上1,那么到处理wi+1的时候,这些边就是无效边了。
也就是说,处理同一wi的边的时候,如果遇到在同一联通的两个点时,如果这个联通块是先前同一wi处理出来的联通块,那么这条边就需要+1,就不用再管这条边了。
克鲁斯卡尔的过程唯一,那么最小生成树就是唯一了。
怎么判断是否是同一wi时候碰上的冲突,先剔除掉已经联通的边的两点的边,剩下的就是要wi时联通的,那么冲突就可以知道肯定是当前wi发生的冲突。
#include<bits/stdc++.h>
//怎么判断是否是同一w时候删的,先剔除掉已经联通的,剩下的就是要w时联通的,那么冲突就可以知道肯定是当前w发生的冲突。
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e5+5;
int fa[MX];
int found(int x){if(fa[x]==x)return x;return fa[x]=found(fa[x]);}
map<int,vector<PII>>ma;
int main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
ma[w].push_back(MP(u,v));
}
int ans=0;
for(auto i:ma){
for(auto j:i.SE){
int fx=found(j.FI),fy=found(j.SE);
if(fx==fy)ans--;
}
for(auto j:i.SE){
int fx=found(j.FI),fy=found(j.SE);
if(fx==fy)ans++;
else fa[fx]=fa[fy];
}
}
cout<<ans;
return 0;
}
K:http://acm.hdu.edu.cn/showproblem.php?pid=2544
花式最短路。(我的dijkstra O(ElogV))
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int c,to;};
vector<no>g[MX];
bool operator<(const no &x,const no &y) {return x.c>y.c;}
int dis[MX];
int main(){
int n,m;
while(cin>>n>>m){
if(n==0)break;
for(int i=1;i<=n;i++)dis[i]=INT_MAX/2,g[i].clear();
while(m--){
int u,v,c;scanf("%d%d%d",&u,&v,&c);
g[u].push_back(no{c,v});
g[v].push_back(no{c,u});
}
priority_queue<no>q;
dis[1]=0;q.push(no{0,1});
while(!q.empty()){
int nowv=q.top().to;q.pop();
for(auto i:g[nowv]){
if(i.c+dis[nowv]<dis[i.to]){
dis[i.to]=i.c+dis[nowv];
q.push(no{dis[i.to],i.to});
}
}
}
printf("%d\n",dis[n]);
}
return 0;
}
L:http://poj.org/problem?id=3268
题意:n头牛在n个不同的地方,去x开party,然后回家,求n头牛中路程最长的(他们很聪明,会选择最短路)。
思路:先求x的单源最短路,然后将所有的边反向,再求x 的单源最短路。然后对于第i头牛,他的路程就是两次x到i的距离的和。
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <climits>
#include <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e5+5;
struct no{int co,to;};
bool operator<(const no &x,const no &y){return x.co>y.co;}
vector<no>g[MX];
int dis[MX][2];
int V[MX][2],c[MX];
int main(){
int n,m,x;cin>>n>>m>>x;
for(int i=1;i<=m;i++)scanf("%d%d%d",&V[i][0],&V[i][1],&c[i]);
for(int k=0;k<=1;k++){
for(int i=1;i<=n;i++)g[i].clear(),dis[i][k]=INT_MAX/2;
for(int i=1;i<=m;i++)g[V[i][k]].push_back(no{c[i],V[i][k^1]});
priority_queue<no>q;
dis[x][k]=0;q.push(no{0,x});
while(!q.empty()){
int nowv=q.top().to;q.pop();
for(int i=0;i<(int)g[nowv].size();i++){
if(g[nowv][i].co+dis[nowv][k]<dis[g[nowv][i].to][k]){
dis[g[nowv][i].to][k]=g[nowv][i].co+dis[nowv][k];
q.push(no{dis[g[nowv][i].to][k],g[nowv][i].to});
}
}
}
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(dis[i][0]+dis[i][1],ans);
cout<<ans<<endl;
return 0;
}
M:http://poj.org/problem?id=3259 //负环
判断给定图是否有负环,有负环,那么小明从某个点出发,可能回到过去。
spfa,某个点入队次数>n,说明有负环
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <climits>
#include <algorithm>
using namespace std;
const int max_n=505;
struct no{int to,cos;};
vector<no>g[max_n];
int d[max_n],inq[max_n];
int vis[max_n];int n,m,q;
int spfa(int s,int t){ //如果入队次数大于n+1,那么有负环(自己手写)
queue<int>q;
q.push(s),d[s]=0,inq[s]=1;vis[s]++;
while(!q.empty()){
int now=q.front();
for(int i=0;i<(int)g[now].size();i++){
int v=g[now][i].to;
if(d[v]>d[now]+g[now][i].cos){ //松弛操作
d[v]=d[now]+g[now][i].cos;
if(inq[v])continue;
inq[v]=1;
q.push(v);
vis[v]++;
if(vis[v]>n+1) return -1;
}
}
q.pop();
inq[now]=0;
}
if(d[t]==INT_MAX/2) return -2;
return d[t];
}
int main(){
int T;cin>>T;while(T--){
cin>>n>>m>>q;
for(int i=1;i<=n;i++)d[i]=INT_MAX/2,inq[i]=0,g[i].clear(),vis[i]=0;
for(int i=1;i<=m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
g[u].push_back(no{v,w});
g[v].push_back(no{u,w});
}
for(int i=1;i<=q;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
g[u].push_back(no{v,-w});
}
if(spfa(1,n)==-1)puts("YES");
else puts("NO");
}
return 0;
}
N:https://codeforces.com/problemset/problem/1076/D //最短路树
题意:给一个联通simple图,问,删到留下小于等于k条边后,最多有多少个点到1的最短距离和没删之前一样,并输出这k条边。
思路:跑dijkstra的时候记录每个点最后的前驱,处理出一个最短路树,然后从1点bfs出k条边。
为什么会有最短路树(脑补dijkstra过程)
dijkstra的时候,每次都会处理出一个点,使这个点加入到已经处理好的集合中(这个点到1的最短路径已经确定)。
每次记录的是每个点的最后的前驱,那么肯定是保证每个点是有前驱。
最后这个通过前驱处理出来的图是联通的。
之前的dijkstra都是不完整的啊!!,没有维护那个最短路已确定的点的集合。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
struct no{LL c,to;};
vector<no>g[MX];
bool operator<(const no &x,const no &y) {return x.c>y.c;}
LL dis[MX];
int pre[MX],ein[MX],vis[MX];
vector<int>G[MX];
map<PII,int>ma;
int main(){
int n,m,k;cin>>n>>m>>k;
for(int i=1;i<=n;i++)dis[i]=LLONG_MAX/2;//!!!
for(int i=1;i<=m;i++){
int u,v,c;scanf("%d%d%d",&u,&v,&c);
g[u].push_back(no{c,v});ma[MP(u,v)]=i;
g[v].push_back(no{c,u});ma[MP(v,u)]=i;
}
priority_queue<no>q;
dis[1]=0;q.push(no{0,1});
while(!q.empty()){
int nowv=q.top().to;q.pop();
if(vis[nowv])continue;
vis[nowv]=1;
for(auto i:g[nowv]){
if(i.c+dis[nowv]<dis[i.to]){
pre[i.to]=nowv;ein[i.to]=ma[MP(i.to,nowv)];
dis[i.to]=i.c+dis[nowv];
q.push(no{dis[i.to],i.to});
}
}
}
for(int i=2;i<=n;i++) G[pre[i]].push_back(i);
int zz=min(n-1,k); cout<<zz<<endl;
queue<int>Q;Q.push(1);
while(!Q.empty()){
int now=Q.front();Q.pop();
for(auto i:G[now]){
if(k==0)return 0;//!!!
cout<<ein[i]<< ' ',Q.push(i), k--;
}
}
return 0;
}
O:https://blog.csdn.net/qq_41730604/article/details/96628900 //lca+最短路