图论题集锦
图论题集锦
最短路
poj3662
题意:求1...n的所有路径中,路径上的边第k+1大最小
做法1:考虑分层图最短路,将每个点设为(x,p)表示到x点p+1边大,那么对于每条边(x,y)就连接了,(x,p) $ -^{w(x,y)} $ (y,p),(x,p) $ -^{0} $ (y,p+1),在这张图上用spfa跑就行了
做法2:考虑二分答案,将第k+1大边的值ans二分,然后对于图上所有的大于ans的边取1,小于的取0,那么只需要在这张图上跑最短路,如果dis[1][n]<=k那么答案就可以,反之不行,跑边权只有01最短路,可以用双端队列,复杂度优化到\(O(n+m)\)
点击查看代码
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1010;
const int M=10010;
int Head[N],Next[M*2],ver[M*2],edge[M*2];
int tot;
int dis[N][N];
int inq[N][N];
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
edge[tot]=w;
}
void spfa(int s,int t,int k){
memset(dis,0x3f,sizeof(dis));
queue< pair<int,int> >q;
q.push({1,0});
dis[1][0]=0;
while(q.size()){
pair<int,int> o=q.front();
q.pop();
int x=o.first,p=o.second;
inq[x][p]=0;
//if(p==k) return ;
for (int i=Head[x];i;i=Next[i]){
int v=ver[i];
if(p<=k && dis[v][p]>max(dis[x][p],edge[i])){
dis[v][p]=max(dis[x][p],edge[i]);
if(!inq[v][p]){
q.push({v,p});
inq[v][p]=1;
}
}
if(p+1<=k && dis[v][p+1]>dis[x][p]){
dis[v][p+1]=dis[x][p];
if(!inq[v][p+1]){
q.push({v,p+1});
inq[v][p+1]=1;
}
}
}
}
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
int u,v,w;
for (int i=1;i<=m;i++) {
scanf("%d%d%d",&u,&v,&w);
Insert(u,v,w);
Insert(v,u,w);
}
spfa(1,n,k);
if(dis[n][k]==0x3f3f3f3f) printf("-1\n");
else printf("%d\n",dis[n][k]);
return 0;
}
[acwing]342. 道路与航线
题意:给一张有负权边的图,求源点到每个点的距离,保证负权边和其他边一起不会构成环
做法:因为题中的负权边的限制,所以可以发现其实将只含正权边连通块缩点后变成了一张DAG,所以在块内做dij,块外top排序做最短路就行了
点击查看代码
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=25010;
const int M=100010;
struct node{
int id,d;
node(int id,int d):id(id),d(d){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int Head[N],Next[M*2],ver[M*2],edge[M*2];
int tot=0,c[N],cnt=0,in[N];
int dis[N],vis[N];
vector<int>id[N];
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
edge[tot]=w;
}
void dfs(int x){
c[x]=cnt;
id[cnt].push_back(x);
for (int i=Head[x];i;i=Next[i]){
int v=ver[i];
if(c[v]) continue;
dfs(v);
}
}
void dij(int s){
memset(dis,0x3f,sizeof(dis));
queue<int>q;
for (int i=1;i<=cnt;i++){
if(in[i]==0) q.push(i);
}
dis[s]=0;
while(!q.empty()){
int t=q.front();q.pop();
priority_queue<node>Q;
for (auto x:id[t])
{Q.push(node(x,dis[x]));vis[x]=0;}
while(!Q.empty()){
int u=Q.top().id;
Q.pop();
if(vis[u]) continue;
vis[u]=1;
for (int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(dis[v]>dis[u]+edge[i]){
dis[v]=dis[u]+edge[i];
if(c[v]==c[u]){
Q.push(node(v,dis[v]));
}
}
if(c[v]!=c[u]){
if(--in[c[v]]==0)
q.push(c[v]);
}
}
}
}
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n,R,P,S;
scanf("%d%d%d%d",&n,&R,&P,&S);
int u,v,w;
for (int i=1;i<=R;i++) {
scanf("%d%d%d",&u,&v,&w);
Insert(u,v,w);
Insert(v,u,w);
}
for (int i=1;i<=n;i++){
if(c[i]==0){
++cnt;
dfs(i);
}
}
for (int i=1;i<=P;i++){
scanf("%d%d%d",&u,&v,&w);
Insert(u,v,w);
in[c[v]]++;
}
dij(S);
for (int i=1;i<=n;i++){
if(dis[i]>=0x3f3f3f3f/2) printf("NO PATH\n");
else printf("%d\n",dis[i]);
}
return 0;
}
poj3463 Sightseeing
做法:求最短路和次短路的路径数,如果只更新最短路那么直接做dij就好,但是还要更新次短路的话就需要把每个点拓展成两个点,因为更新路径u->v时,v也可以被u的次短路更新到
点击查看代码
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1010;
const int M=10010;
struct node{
int id,d,k;
node(int id,int d,int k):id(id),d(d),k(k){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int Head[N],Next[M*2],ver[M*2],edge[M*2];
int tot=0;
int dis[2][N],vis[2][N],cnt[2][N];
void Init(){
memset(Head,0,sizeof(Head));
memset(Next,0,sizeof(Next));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
memset(edge,0,sizeof(edge));
memset(ver,0,sizeof(ver));
tot=0;
}
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
edge[tot]=w;
}
void dij(int s){
memset(dis,0x3f,sizeof(dis));
dis[0][s]=0;
cnt[0][s]=1;
priority_queue<node>Q;
Q.push(node(s,0,0));
while(!Q.empty()){
int u=Q.top().id;
int k=Q.top().k;
int d=Q.top().d;
Q.pop();
if(vis[k][u]) continue;
vis[k][u]=1;
for (int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(dis[0][v]>d+edge[i] && k==0){
dis[1][v]=dis[0][v];
cnt[1][v]=cnt[0][v];
dis[0][v]=d+edge[i];
cnt[0][v]=cnt[k][u];
Q.push(node(v,dis[0][v],0));
Q.push(node(v,dis[1][v],1));
}else if(dis[0][v]==d+edge[i] && k==0) cnt[0][v]+=cnt[k][u];
else if(dis[1][v]>d+edge[i]){
dis[1][v]=d+edge[i];
cnt[1][v]=cnt[k][u];
Q.push(node(v,dis[1][v],1));
}else if(dis[1][v]==d+edge[i]) cnt[1][v]+=cnt[k][u];
}
}
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int T;
scanf("%d",&T);
while(T--){
Init();
int n,m,u,v,w;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
Insert(u,v,w);
}
int s,b;
scanf("%d%d",&s,&b);
dij(s);
int ans=cnt[0][b];
if(dis[0][b]+1==dis[1][b]) ans+=cnt[1][b];
//if(dis[0][b]==0x3f3f3f3f) ans=0;
printf("%d\n",ans);
}
return 0;
}
POJ - 1860 Currency Exchange
题意:给两点之间的汇率和税,问给你初始资本,最后换一圈后能不能赚到钱
做法:如果能赚钱的话一定存在正的一个环,所以直接spfa最长路检测是否有正环就ok
点击查看代码
#include <cstdio>
#include <queue>
using namespace std;
const int N=110;
const int M=1010;
int Head[N],Next[M],ver[M*2];
double rate[M*2],cost[M*2];
int tot;
void Insert(int u,int v,double rat,double w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
rate[tot]=rat;
cost[tot]=w;
}
double dis[N],cnt[N],inq[N];
bool spfa(int s,double V,int n){
for (int i=1;i<=n;i++) dis[i]=-1e8;
queue<int>q;
q.push(s);
dis[s]=V;
inq[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
inq[x]=0;
for (int i=Head[x];i;i=Next[i]){
int v=ver[i];
double d=(dis[x]-cost[i])*rate[i];
if(dis[v]<d){
dis[v]=d;
cnt[v]=cnt[x]+1;
if(!inq[v]) {inq[v]=1,q.push(v);}
if(cnt[v]>n) return true;
}
}
}
return false;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n,m,s;
double v;
scanf("%d%d%d%lf",&n,&m,&s,&v);
for (int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
double rat,w;
scanf("%lf%lf",&rat,&w);Insert(x,y,rat,w);
scanf("%lf%lf",&rat,&w);Insert(y,x,rat,w);
}
if(spfa(s,v,n)) puts("YES");
else puts("NO");
return 0;
}
POJ - 3159Candies
做法:经典差分约束,\(x_i-x_j<=k\) 由\(x_j\)向\(x_i\)连边,求最短路为d[n]-d[1]最大解,最长路为最小解
点击查看代码
#include <cstdio>
#include <queue>
using namespace std;
const int N=30010;
const int M=150000+10;
int Head[N],Next[M*2],ver[M*2];
int cost[M*2];
int tot;
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
cost[tot]=w;
}
struct node{
int d,id;
node(int id,int d):id(id),d(d){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int dis[N],cnt[N],vis[N];
int dij(int s,int n){
for (int i=1;i<=n;i++) dis[i]=1e9;
priority_queue<node>q;
q.push(node(s,0));
dis[s]=0;
vis[s]=0;
while(!q.empty()){
int u=q.top().id;q.pop();
if(vis[u]) continue;
vis[u]=1;
for (int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(dis[v]>dis[u]+cost[i]){
dis[v]=dis[u]+cost[i];
q.push(node(v,dis[v]));
}
}
}
return dis[n];
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
Insert(u,v,w);
}
printf("%d\n",dij(1,n));
return 0;
}
POJ - 1062昂贵的聘礼
题意:就是买一个东西的时候可以用另外一个东西来优惠,那么价格就变成了优惠价格,但是要求所有用来兑换的东西等级差要在T之内
做法:直接枚举最底等级是多少,把最低和最高等级限制,跑最短路即可
点击查看代码
#include <cstdio>
#include <queue>
using namespace std;
const int N=110;
const int M=150000+10;
const int nil=0x7fffffff;
int Head[N],Next[M*2],ver[M*2];
int cost[M*2];
int tot;
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
cost[tot]=w;
}
struct node{
int d,id;
node(int id,int d):id(id),d(d){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int dis[N],cnt[N],vis[N];
int W[N],level[N];
void dij(int s,int n,int low,int high){
for (int i=1;i<=n;i++) vis[i]=0,dis[i]=nil;
priority_queue<node>q;
q.push(node(s,0));
dis[s]=0;
vis[s]=0;
while(!q.empty()){
int u=q.top().id;q.pop();
if(vis[u]) continue;
vis[u]=1;
for (int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(level[v]<low || level[v]>high) continue;
if(dis[v]>dis[u]+cost[i]){
dis[v]=dis[u]+cost[i];
q.push(node(v,dis[v]));
}
}
}
return ;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int T,n;
scanf("%d%d",&T,&n);
for (int i=1;i<=n;i++){
int m;
scanf("%d%d%d",&W[i],&level[i],&m);
for (int j=1;j<=m;j++){
int id,v;
scanf("%d%d",&id,&v);
Insert(i,id,v);
}
}
int ans=nil;
for (int l=max(level[1]-T,0);l<=level[1];l++){
dij(1,n,l,l+T);
for (int i=1;i<=n;i++){
if(dis[i]<nil)
ans=min(ans,dis[i]+W[i]);
}
}
printf("%d\n",ans);
return 0;
}
LightOJ - 1074
题意:给n个点的有向图,两点之间的距离可能是负值,找出从1节点到其它节点的距离,如果距离小于3或者无法到达,输出?
做法:就是直接的spfa找负环,但是要注意的是在找的时候与负环相连的所有点必然最后最短路小于3,所以需要标记,还有图上可能不止一个负环,所以将负环上的某个点标记出来,还要继续找负环
HDU - 4370
题意:就是给一个矩阵,给定
\(1.X12+X13+...X1n=1\)
\(2.X1n+X2n+...Xn-1n=1\)
\(3.for each i (1<i<n), satisfies ∑Xki (1<=k<=n)=∑Xij (1<=j<=n).\)
然后构造这个矩阵,使得给定矩阵,\(\sum_{1<=i<=n,1<=j<=n}c[i][j]*x[i][j]\),最大
做法:这其实主要在于看懂题意,如果看成有n个点的话,题目其实就是再说,选1必须选2,选2必须选...,最后必须选到n,那么就是说1必须有
一个出度,n必须有一个入度,那么不就是等价于,按给的邻接矩阵建图跑1...n的最短路吗,但是,由于题目只是要求1必须有一个出度,n必须有一个入度,所以还需要考虑是1的环+n的环的特殊情况,两者取min,将源点设为dis[s]=nil,并且把和源点相连的点先加到队里面,就可以求出成环回到源点的最短路了,
点击查看代码
#include <cstdio>
#include <queue>
#include <iostream>
#include <cstring>
using namespace std;
const int N=310;
const int M=90100;
const int nil=0x7fffffff;
int Head[N],Next[M*2],ver[M*2];
int cost[M*2];
int tot;
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
cost[tot]=w;
}
void init(int n){
memset(Head,0,sizeof(Head));
memset(Next,0,sizeof(Next));
tot=0;
}
struct node{
int d,id;
node(int id,int d):id(id),d(d){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int d1[N],d2[N],vis[N];
void dij(int s,int n,int *dis){
for (int i=1;i<=n;i++) vis[i]=0,dis[i]=nil;
priority_queue<node>q;
for (int i=Head[s];i;i=Next[i]){
q.push(node(ver[i],cost[i]));
dis[ver[i]]=cost[i];
}
while(!q.empty()){
int u=q.top().id;q.pop();
if(vis[u]) continue;
vis[u]=1;
for (int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(dis[v]>dis[u]+cost[i]){
dis[v]=dis[u]+cost[i];
q.push(node(v,dis[v]));
}
}
}
return ;
}
int a[N][N];
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int n;
while(scanf("%d",&n)!=EOF){
init(n);
for (int i=1;i<=n;i++) {
for (int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
}
int ans2=nil;
int ans3=nil;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if((i==1 && j==1) || (i==n && j==n)) continue;
Insert(i,j,a[i][j]);
}
}
dij(1,n,d1);
dij(n,n,d2);
int ans1=d1[1]+d2[n];
int ans=d1[1];
printf("%d\n",min(ans,ans1));
}
return 0;
}
HDU - 3416
题意:求任何一条边都不相交的A到B最短路方案最多有多少条
做法:将A,B最短路上的边容量设为1,然后跑最大流,怎么找所有最短路上的边呢,类似最短路径树,我们可以遍历所有的边,如果满足dis[v]=dis[u]+edge[i]的就是最短路上的边
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
const int M=2e5+10;
const int nil=0x7fffffff;
class Map{
public:
int Head[N],Next[M],ver[M],edge[M];
int tot;
void init(){
tot=1;
memset(Head,0,sizeof(Head));
memset(Next,0,sizeof(Next));
}
void Insert(int u,int v,int w){
ver[++tot]=v;
Next[tot]=Head[u];
Head[u]=tot;
edge[tot]=w;
}
};
Map e1,e2;
struct node{
int d,id;
node(int id,int d):id(id),d(d){}
bool operator<(const node &rhs)const{
return d>rhs.d;
}
};
int dis[N],vis[N];
void dij(int s,int n){
for (int i=1;i<=n;i++) vis[i]=0,dis[i]=nil;
priority_queue<node>q;
q.push(node(s,0));
dis[s]=0;
while(!q.empty()){
int u=q.top().id;q.pop();
if(vis[u]) continue;
vis[u]=1;
for (int i=e1.Head[u];i;i=e1.Next[i]){
int v=e1.ver[i];
if(dis[v]>dis[u]+e1.edge[i]){
dis[v]=dis[u]+e1.edge[i];
q.push(node(v,dis[v]));
}
}
}
return ;
}
void build(int s,int t,int n){
for (int x=1;x<=n;x++){
for (int i=e1.Head[x];i;i=e1.Next[i]){
int v=e1.ver[i];
if(dis[v]==dis[x]+e1.edge[i]){
//printf("%d %d\n",x,v);
e2.Insert(x,v,1);
e2.Insert(v,x,0);
}
}
}
}
int d[N];
bool bfs(int s,int t){
memset(d,0,sizeof(d));
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for (int i=e2.Head[x];i;i=e2.Next[i]){
if(e2.edge[i] && !d[e2.ver[i]]){
q.push(e2.ver[i]);
d[e2.ver[i]]=d[x]+1;
if(e2.ver[i]==t) return 1;
}
}
}
return 0;
}
int dfs(int x,int t,int flow){
if(x==t) return flow;
int rest=flow,k;
for (int i=e2.Head[x];i && rest;i=e2.Next[i]){
if(e2.edge[i] && d[e2.ver[i]]==d[x]+1){//分层图优化
k=dfs(e2.ver[i],t,min(rest,e2.edge[i]));
if(!k) d[e2.ver[i]]=0;
e2.edge[i]-=k;
e2.edge[i^1]+=k;
rest-=k;
}
}
return flow-rest;//一开始flow==rest,所以二者差值就是流量
}
int dinic(int s,int t){
int maxflow=0;
int flow;
while(bfs(s,t)) while(flow=dfs(s,t,nil)) maxflow+=flow;
return maxflow;
}
int main(){
#ifdef lmj_debug
freopen("1.in","r",stdin);
#endif
int T;
cin>>T;
while(T--){
int n,m;
scanf("%d%d",&n,&m);
e1.init();
e2.init();
for (int i=1;i<=m;i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e1.Insert(u,v,w);
}
int A,B;
scanf("%d%d",&A,&B);
dij(A,n);
build(A,B,n);
printf("%d\n",dinic(A,B));
}
return 0;
}