【POJ 2253 Frogger】
这道题求从u到v中所有通路的最大边最小
我直接二分做了,但实际上这种题是最短路算法的变种,意义在于告诉我们spfa这些算法不仅能维护出最短路,稍加修改后可以维护出很多其他东西。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 2e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int v,d; node(int v1,int d1): v(v1),d(d1) {} }; vector<node> edges[MAXN]; int dist[MAXN],visit[MAXN],n; void spfa(){ for(int i=1;i<=n;i++) dist[i]=INF; dist[1]=0; visit[1]=1; queue<int> q; q.push(1); while( !q.empty() ){ int u = q.front(); q.pop(); visit[u]=0; for(int i=0;i<edges[u].size();i++){ int v = edges[u][i].v; if( dist[v]>dist[u]+edges[u][i].d ){ dist[v] = dist[u]+edges[u][i].d; if( !visit[v] ) q.push(v); } } } } int x[MAXN],y[MAXN]; double dis[MAXN][MAXN]; int main(){ //ios::sync_with_stdio(false); int tc=0; while(1){ scanf("%d",&n); if(n==0) break; for(int i=1;i<=n;i++) scanf("%d%d",x+i,y+i); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ double x1 = abs(x[i]-x[j]); double y1 = abs(y[i]-y[j]); dis[i][j] = sqrt(x1*x1 + y1*y1); //cout<<i<<" "<<j<<" "<<dis[i][j]<<endl; } } double start=0,end=3000,mid; while( end-start>=0.00001 ){ mid = (start+end)/2; for(int i=1;i<=n;i++) edges[i].clear(); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if( mid>=dis[i][j] ) edges[i].push_back( node(j,1) ); } } spfa(); if( dist[2]!=INF ) end=mid; else start=mid; } printf("Scenario #%d ",++tc); printf("Frog Distance = %.3lf ",end); printf(" "); } return 0; }
【POJ 1797 Heavy Transportation】
维护所有通路的最小边最大
那修改后的松弛操作就是 dist[v] = max( dist[v], min( dist[u], edges[u][i].d ) )
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e3 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int v,d; node(int v1,int d1): v(v1),d(d1) {} }; vector<node> edges[MAXN]; int dist[MAXN],visit[MAXN],n,m;//dist[i]代表从源点到i点中所有通路的最小边最大值 void spfa(){ for(int i=1;i<=n;i++) dist[i]=0; dist[1]=INF; visit[1]=1; queue<int> q; q.push(1); while( !q.empty() ){ int u = q.front(); q.pop(); visit[u]=0; for(int i=0;i<edges[u].size();i++){ int v = edges[u][i].v; int weight = min( edges[u][i].d,dist[u] );//weight是这条通路上的最小边 if( weight>dist[v] ){ dist[v] = weight; if( !visit[v] ) { //visit[v]=1; q.push(v); } } } } } //找所有通路中最小边最大的 int main(){ //ios::sync_with_stdio(false); int t,tc=0; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int u,v,d; scanf("%d%d%d",&u,&v,&d); edges[u].push_back( node(v,d) ); edges[v].push_back( node(u,d) ); } spfa(); printf("Scenario #%d: ",++tc); printf("%d ",dist[n]); printf(" "); for(int i=1;i<=n;i++) edges[i].clear(); } return 0; }
【POJ 3268 Silver Cow Party】
想到跑flyod但n^3太慢就t了,实际上反向建边跑两边spfa就可以了!
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e3 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int v,d; node(int v1,int d1): v(v1),d(d1) {} }; vector<node> edges[MAXN]; int vis[MAXN],dist1[MAXN],dist2[MAXN],n,m,x; void spfa1(){ for(int i=1;i<=n;i++) dist1[i]=INF; queue<int> q; q.push(x); vis[x]=1; dist1[x]=0; while(!q.empty()){ int u = q.front(); q.pop(); vis[u]=0; for(int i=0;i<edges[u].size();i++){ int v = edges[u][i].v; int d = edges[u][i].d; if( dist1[u]+d<dist1[v] ){ dist1[v]=dist1[u]+d; if( !vis[v] ){ q.push(v); vis[v]=1; } } } } } void spfa2(){ for(int i=1;i<=n;i++) dist2[i]=INF; queue<int> q; q.push(x); vis[x]=1; dist2[x]=0; while(!q.empty()){ int u = q.front(); q.pop(); vis[u]=0; for(int i=0;i<edges[u].size();i++){ int v = edges[u][i].v; int d = edges[u][i].d; if( dist2[u]+d<dist2[v] ){ dist2[v]=dist2[u]+d; if( !vis[v] ){ q.push(v); vis[v]=1; } } } } } int u[100005],v[100005],d[100005]; int main(){ //ios::sync_with_stdio(false); while( scanf("%d%d%d",&n,&m,&x)!=EOF ){ for(int i=1;i<=m;i++){ scanf("%d%d%d",u+i,v+i,d+i); edges[ u[i] ].push_back( node(v[i],d[i]) ); } spfa1(); for(int i=1;i<=n;i++) edges[i].clear(); for(int i=1;i<=m;i++) edges[ v[i] ].push_back( node(u[i],d[i]) ); spfa2(); int ans=-1; for(int i=1;i<=n;i++){//枚举所有cows ans = max( ans,dist1[i]+dist2[i] ); } printf("%d ",ans); for(int i=1;i<=n;i++) edges[i].clear(); } return 0; }
【POJ 1860 Currency Exchange】
题目问的是找正环,那改一下dist维护的数值就行了。
但我一开始没看出来,所以写了bfs+剪枝也过了。后来我发现spfa好像也就是bfs+剪枝啊!维护的dist数组就是过程性剪枝,松弛就是剪枝的具体操作
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct exchange{ int type; double r,c; exchange(int t1,double r1,double c1): type(t1),r(r1),c(c1) {} }; vector<exchange> edges[MAXN];//edges[i]是拿着i货币能选择的一些兑换方案 double d[MAXN],money; int n,m,s; struct node{ int type; double m;//状态是拿着m个type钱 node(int t1,double m1): type(t1),m(m1) {} }; bool bfs(){ for(int i=1;i<=n;i++) d[i]=-INF;//最优性剪枝,d[i]为i种货币最多有多少个 queue<node> q; q.push( node(s,money) ); d[s]=money; while( !q.empty() ){ node p = q.front(); q.pop(); if( p.type==s && p.m>money ) return true; for(int i=0;i<edges[p.type].size();i++){//能怎么换 exchange e = edges[p.type][i];//在这个所换 double m1 = (p.m-e.c)*e.r; if( m1>d[ e.type ] ){ d[e.type]=m1; q.push( node(e.type,m1) ); } } } return false; } int main(){ //ios::sync_with_stdio(false); while( scanf("%d%d%d%lf",&n,&m,&s,&money)!=EOF ){ for(int i=1;i<=m;i++){ int a,b; double r1,c1,r2,c2; scanf("%d%d%lf%lf%lf%lf",&a,&b,&r1,&c1,&r2,&c2); edges[a].push_back( exchange(b,r1,c1) ); edges[b].push_back( exchange(a,r2,c2) ); } if( bfs() ) printf("YES "); else printf("NO "); for(int i=1;i<=n;i++) edges[i].clear(); } return 0; }
【POJ 3660 Cow Contest】
难点在于建模,b比a弱,就建一条有向边从b到a。定义a能确定排名的意思为对于所有的其他人,要么我能到他(我比他弱),要么他能到我(我比他强),所以跑一次flyod就行了
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; ll dist[MAXN][MAXN]; int main(){ //ios::sync_with_stdio(false); int n,m; while( scanf("%d%d",&n,&m)!=EOF ){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if( i==j ) continue; else dist[i][j]=INF; } } for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); //u比v强,从v到u有一条边 dist[v][u]=1; } for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ dist[i][j] = min( dist[i][j],dist[i][k]+dist[k][j] ); } } } int ans=0; for(int i=1;i<=n;i++){ bool flag=true;//假设排名确定 for(int j=1;j<=n;j++){ if( dist[i][j]!=INF || dist[j][i]!=INF ) continue; flag=false; } if( flag ) ans++; } printf("%d ",ans); } return 0; }
【POJ 2240 Arbitrage】
找正环模板
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; map<string,int> mp; struct node{ int v; double rate; node(int v1,double r): v(v1),rate(r) {} }; vector<node> edges[MAXN]; double dist[MAXN];//dist[i]指的是从起点到 i 所拥有的最多i货币个数 int n,m; int vis[MAXN],update[MAXN]; bool spfa(){//找正环 memset(dist,0,sizeof(dist)); memset(vis,0,sizeof(vis)); memset(update,0,sizeof(update)); queue<int> q; q.push(1); vis[1]=1; update[1]++; dist[1]=1; while( !q.empty() ){ int u=q.front(); q.pop(); //cout<<u<<endl; //for(int i=1;i<=n;i++) cout<<dist[i]<<" "; cout<<endl<<endl; vis[u]=0; for(int i=0;i<edges[u].size();i++){ double r = edges[u][i].rate; int v = edges[u][i].v; if( dist[u]*r >= dist[v] ){ dist[v] = dist[u]*r; if( !vis[v] ){ q.push(v); vis[v]=1; update[v]++; if( update[v]>=n ) return true; } } } } return false; } int main(){ ios::sync_with_stdio(false); int tc=0; while(1){ cin>>n; if(n==0) break; for(int i=1;i<=n;i++){ string s; cin>>s; mp[s]=i;//s货币的id是i } cin>>m; for(int i=1;i<=m;i++){ string a,b; double rate; cin>>a>>rate>>b; edges[ mp[a] ].push_back( node(mp[b],rate) ); } //Case 1: Yes if( spfa() ) cout<<"Case "<<++tc<<": Yes"<<endl; else cout<<"Case "<<++tc<<": No"<<endl; for(int i=1;i<=n;i++) edges[i].clear(); } return 0; }
【POJ 3159 Candies】
首先要建出模型题目问的是1到n的最短路(维护dist数组,dist[i]表示1号同学最多可以比i同学少多少颗糖果,然后发现松弛的方法跟最短路方法一样)
之后是卡常,通过看题解知道要用前向星+栈维护才可以过,算是体现出了spfa的不稳定性和其算法本质是搜索(模拟dfs)。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 3e4 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct Edge{ int v,d,next; }edges[150000]; int head[MAXN],cnt; void add_edge(int u,int v,int d){//建一条从u到v的边 edges[++cnt].next = head[u]; head[u]=cnt; edges[cnt].v = v; edges[cnt].d = d; } int dist[MAXN],vis[MAXN],n,m;//dist[i]代表snoopy最多比i同学少多少颗糖果 void spfa(){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) dist[i]=INF; stack<int> q; while( !q.empty() ) q.pop(); q.push(1); vis[1]=1; dist[1]=0; while( !q.empty() ){ int u=q.top(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int d = edges[i].d; int v = edges[i].v; if( dist[v]>dist[u]+d ){ dist[v] = dist[u]+d; if( !vis[v] ){ q.push(v); vis[v]=1; } } } } } int main(){ //ios::sync_with_stdio(false); while( scanf("%d%d",&n,&m)!=EOF ){ cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++){ int u,v,d; scanf("%d%d%d",&u,&v,&d); add_edge(u,v,d); } spfa(); printf("%d ",dist[n]); } return 0; }
【POJ 2502 Subway】
根据题意建图就行,输入比题难写。poj里g++会wa,但c++就过了
#include<iostream> #include<cstdio> #include<cstring> #include<math.h> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 2e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct stop{ int x,y; stop(int x1=0,int y1=0):x(x1),y(y1) {} }stops[MAXN]; vector<stop> line; struct edge{ int v,next;//next指的是相同u情况下的下一条边 double d; }edges[100000]; int head[MAXN],cnt; void add_edge(int u,int v,double d){ edges[++cnt].next=head[u]; edges[cnt].v=v; edges[cnt].d=d; head[u] = cnt; } double dist[MAXN]; int vis[MAXN]; void spfa(){ for(int i=1;i<MAXN;i++) dist[i]=INF; queue<int> q; q.push(1); vis[1]=1; dist[1]=0; while( !q.empty() ){ int u = q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int v = edges[i].v; double d = edges[i].d; if( dist[v]>dist[u]+d ){ dist[v] = dist[u]+d; if( !vis[v] ){ vis[v]=1; q.push(v); } } } } } int main(){ //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //1是起点,2是终点 int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2 ); stops[1] = stop(x1,y1); stops[2] = stop(x2,y2); int xcha = abs(x1-x2); int ycha = abs(y1-y2); double da = sqrt( 1.0*xcha*xcha+1.0*ycha*ycha )/10000.0*60; add_edge(1,2,da); add_edge(2,1,da); int id=2,num=0; while( scanf("%d%d",&x1,&y1)!=EOF ){ if( x1==-1 && y1==-1 ) num=0;//目前lines上有几个 else{ num++; id++;//目前stop的id stops[id]=stop(x1,y1); //将它与lines上的上一个stop建双向边,与所有不在lines上的其他stop建双向边 if( num>1 ){ for(int i=1;i<id-1;i++){ int x = abs(stops[i].x-x1); int y = abs(stops[i].y-y1); double dis = sqrt( 1.0*x*x+1.0*y*y )/10000.0*60; add_edge(i,id,dis); add_edge(id,i,dis); } int x = abs(stops[id-1].x-x1); int y = abs(stops[id-1].y-y1); double dis = sqrt( 1.0*x*x+1.0*y*y )/40000.0*60; add_edge(id-1,id,dis); add_edge(id,id-1,dis); } else{ for(int i=1;i<id;i++){ int x = abs(stops[i].x-x1); int y = abs(stops[i].y-y1); double dis = sqrt( 1.0*x*x+1.0*y*y )/10000.0*60; add_edge(i,id,dis); add_edge(id,i,dis); } } } } spfa(); printf("%.0lf ",dist[2] ); return 0; }
【POJ 1062 昂贵的婚礼】
真的是什么算法都能跟暴力扯上关系。。以后要多长个心眼,就像二分一样,枚举也是ACM常用的套路一种啊!
逐渐的建图有点感觉了,这题的难点在于解决【等级限制】的问题。kuangbin专题里的分水槛的题终于来了
我一开始想的是改变边的构造,想的是如果某某交易发生了,那就自动有一些边之后不能再走。这个感觉是对的,但没办法实现。实际上可以从点出发去考虑,考虑哪些点会在这个图里出现,那么等级差不超过M的点会在图里出现。再加上已知level[1](酋长的等级)必须在图里出现,我们从[ level[1]-m, level[1] ], [ level[1]-m+1, level[1]+1 ] , ... 枚举到 [ level[1] , level[1]+M ]就行了。那么每次找最短路就只考虑主人等级落在这个区间里的点
#include<iostream> #include<cstdio> #include<cstring> #include<math.h> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct edge{ int v,next;//next指的是相同u情况下的下一条边 int d; }edges[100000]; int head[MAXN],cnt; void add_edge(int u,int v,int d){ edges[++cnt].next=head[u]; edges[cnt].v=v; edges[cnt].d=d; head[u] = cnt; } int dist[MAXN],vis[MAXN],include[MAXN],n,m; void spfa(){ for(int i=1;i<=n;i++) dist[i]=INF; queue<int> q; q.push(0); vis[0]=1; dist[0]=0; while( !q.empty() ){ int u = q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int v = edges[i].v; int d = edges[i].d; if( dist[v]>dist[u]+d && include[v] ){ dist[v] = dist[u]+d; if( !vis[v] ){ vis[v]=1; q.push(v); } } } } } int level[MAXN]; int main(){ //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //0是起点 代表什么都没有 //1是终点 代表有了聘礼 while( scanf("%d%d",&m,&n)!=EOF ){ for(int i=1;i<=n;i++){ int P,L,X; scanf("%d%d%d",&P,level+i,&X); add_edge(0,i,P);//不用优惠政策楞头买 for(int j=1;j<=X;j++){//X个替代品 int t,v; scanf("%d%d",&t,&v); add_edge(t,i,v);//可以花v的钱,从拥有t变成拥有i } } int ans=INF; for(int i=level[1]-m;i<=level[1];i++){ for(int j=1;j<=n;j++){ if( level[j]<i || level[j]>i+m ) include[j]=0;//j结点不考虑在图里面 else include[j]=1; } spfa(); ans=min(ans,dist[1]); } printf("%d ",ans); cnt=0; } return 0; }
【HDU 4725 The Shortest Path in Nya Graph】
我一开始直接耿直建图了,开个vector每层的结点往上下两层建边,那直接边的数量变成n^2级别的了,所以tle。要建图优化
就是每层抽象成一个结点,这样一共有2*n个结点,其中1-n代表的是层结点,n+1-n+n代表的才是正常的结点。
那建图就是每层层结点之间建边代价c(双向边所以共2*n条)
此外再由于【每层的结点是汇点】(指的是层结点的入度只能由非本层的结点指过来),所以向本层的所有其他结点建单向代价为0的边(共n条)
然后原本的结点要向上一层或下一层的层结点建代价为c的边(共2*n条,严格点第一层和最后一层单独考虑,但无所谓了)
所以这么下来5*n级别的边就建完图了,稠密图优化成稀疏图!
还有用spfa的话【建图的顺序】会被卡!如果最后建层结点之间的边会tle,但先建就能ac。玄学。。
#include<iostream> #include<cstdio> #include<cstring> #include<math.h> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 2e5 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct edge{ int v,next;//next指的是相同u情况下的下一条边 int d; }edges[10*MAXN]; int head[MAXN],cnt,a[MAXN]; void add_edge(int u,int v,int d){ edges[++cnt].next=head[u]; edges[cnt].v=v; edges[cnt].d=d; head[u] = cnt; } int dist[MAXN],vis[MAXN],n,m,c,layer[MAXN]; void spfa(){ for(int i=1;i<=2*n;i++) dist[i]=INF; memset(vis,0,sizeof(vis)); queue<int> q; q.push(n+1); vis[n+1]=1; dist[n+1]=0; while( !q.empty() ){ int u = q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int v = edges[i].v; int d = edges[i].d; if( dist[v]>dist[u]+d ){ dist[v] = dist[u]+d; if( !vis[v] ){ vis[v]=1; q.push(v); } } } } } int flag[MAXN]; int main(){ //ios::sync_with_stdio(false); //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); int t,tc=0; scanf("%d",&t); //1到n是layor结点,n+1到n+n才是其他结点 while( t-- ){ cnt=0; memset(head,0,sizeof(head)); memset(flag,0,sizeof(flag)); scanf("%d%d%d",&n,&m,&c); for(int i=1;i<=n;i++){//2*n scanf("%d",layer+i); flag[ layer[i] ]=1; } //这两个建边交换顺序的话会tle。。。 for(int i=1;i<n;i++){ if( flag[i] && flag[i+1] ){ add_edge(i,i+1,c); add_edge(i+1,i,c); } } for(int i=1;i<=n;i++){ add_edge(layer[i],n+i,0); if( layer[i]==1 ) add_edge(n+i,2,c); else if( layer[i]==n ) add_edge(n+i,n-1,c); else{ add_edge(n+i,layer[i]+1,c); add_edge(n+i,layer[i]-1,c); } } // for(int i=1;i<=m;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); add_edge(u+n,v+n,w); add_edge(v+n,u+n,w); } spfa(); if( dist[n+n]==INF ) dist[n+n]=-1; printf("Case #%d: %d ",++tc,dist[n+n]); } return 0; }
【HDU 3416 Marriage Match IV】
被搞自闭。。
但做这一题的收获真的大,练习了写dij想明白复杂度是m+vlogv,相比而言spfa是km,所以相较之下还是dij最稳,其实做了上一题也是让我正视了spfa,将其打下神坛。
然后是会了生成一个最短路图,使得原图的所有最短路都包含在其中。也就是满足dist1[u] + c + dist2[v] == dist1[B]的边在最短路上(因为如果一条边在最短路上,那该最短路一定有 A->u + u->v + v->B的形式。)
接下来跑最大流就好了,写最大流的时候发现了一个问题,一是拿dfs,bfs写的话会比较好;因为拿stack模拟dfs写的话,若用邻接表增广的时候比较麻烦(因为你不知道用的是哪条边,开个vector不断记录就不优雅);若用邻接矩阵的会费用流重边就没法做了。所以拿dfs+bfs最好。其次学会了炸点优化也觉得挺妙的,具体实现的时候由于理解不深还遇到并解决了一个问题。
就是如果在这里写edges[i].cap的话会tle,而且会导致死循环而不是常数上的变慢,为什么呢?
因为如果这条边的cap<0的话,仅仅代表的是这一个弧用不了,那next的可能还可以啊,所以不能退出循环。会死循环是因为bfs的时候发现能到汇点,但dfs的时候又有的边走不到所以走不到汇点增广为0,因此就卡在那了。
#include<iostream> #include<cstring> #include<queue> #define inf 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int maxn = 2e3 + 10; const int maxm = 2e5 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; int n,m,cnt,head[maxn],s,t; int dist1[maxn],dist2[maxn],vis[maxn]; struct edge{ int v,next,d; }edges[maxm]; void addedge(int u,int v,int d){ cnt++; edges[cnt].v=v; edges[cnt].d=d; edges[cnt].next=head[u]; head[u]=cnt; } struct node{ int v,dis; node(int v1,int d1): v(v1),dis(d1) {} bool operator > (const node &p)const{ return dis>p.dis; } }; void dij1(){ for(int i=1;i<=n;i++) dist1[i]=inf; memset(vis,0,sizeof(vis)); priority_queue<node,vector<node>,greater<node> > q; while(!q.empty()) q.pop(); q.push(node(s,0)); dist1[s]=0; while( !q.empty() ){ node u=q.top(); q.pop(); if( vis[u.v] ) continue; vis[u.v]=1; for(int i=head[u.v];i;i=edges[i].next){ int v=edges[i].v; if( dist1[v]>dist1[u.v]+edges[i].d ){ dist1[v]=dist1[u.v]+edges[i].d; q.push(node(v,dist1[v])); } } } } void dij2(){ for(int i=1;i<=n;i++) dist2[i]=inf; memset(vis,0,sizeof(vis)); priority_queue<node,vector<node>,greater<node> > q; while(!q.empty()) q.pop(); q.push(node(t,0)); dist2[t]=0; while( !q.empty() ){ node u=q.top(); q.pop(); if( vis[u.v] ) continue; vis[u.v]=1; for(int i=head[u.v];i;i=edges[i].next){ int v=edges[i].v; if( dist2[v]>dist2[u.v]+edges[i].d ){ dist2[v]=dist2[u.v]+edges[i].d; q.push(node(v,dist2[v])); } } } } int u[maxm],v[maxm],c[maxm]; struct Edge{ int v,next,cap,reverse; }Edges[2*maxm]; void add_edge(int u,int v,int cap){ cnt++; Edges[cnt].v=v; Edges[cnt].cap=cap; Edges[cnt].next=head[u]; Edges[cnt].reverse=cnt+1; head[u]=cnt; cnt++; Edges[cnt].v=u; Edges[cnt].cap=0; Edges[cnt].next=head[v]; Edges[cnt].reverse=cnt-1; head[v]=cnt; } int layor[maxn]; bool bfs(){ memset(layor,-1,sizeof(layor)); queue<int> q; while(!q.empty()) q.pop(); q.push(s); layor[s]=0; while( !q.empty() ){ int u=q.front(); q.pop(); for(int i=head[u];i;i=Edges[i].next){ int v=Edges[i].v; if( layor[v]==-1 && Edges[i].cap ){ layor[v]=layor[u]+1; if( v==t )return true; else q.push(v); } } } return false; } int dfs(int u,int lim){//lim是目前的最大流量,返回要流多少 if( u==t || lim==0 ) return lim; int flow=0;//目前要流0 for(int i=head[u];i;i=Edges[i].next){ Edge &e = Edges[i]; if( layor[e.v]==layor[u]+1 && Edges[i].cap ){ int x=dfs(e.v,min(lim,e.cap)); lim-=x; flow+=x; e.cap-=x; Edges[e.reverse].cap+=x; } } if( !flow ) layor[u]=-2; return flow; } int maxflow(){ int ans=0; while( bfs() ) ans+=dfs(s,inf); return ans; } int main(){ int T; scanf("%d",&T); while( T-- ){ scanf("%d%d",&n,&m); cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++){ scanf("%d%d%d",u+i,v+i,c+i); addedge(u[i],v[i],c[i]); } scanf("%d%d",&s,&t); dij1(); cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++) addedge(v[i],u[i],c[i]); dij2(); cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++){ // s->u[i] , u[i]->v[i] , v[i]->t if( dist1[u[i]]+c[i]+dist2[v[i]]==dist1[t] ) add_edge(u[i],v[i],1); } printf("%d ",maxflow()); } return 0; }
【POJ 4370 0 or 1】
智商题啊。。
考虑x矩阵中x[i][j]=1在图里面的意义是i和j之间的边被走,那么x矩阵里的第i行代表第i个点的出度情况,第i列代表第i个点的入度情况:
1.第一行除第一个外有且有一个1 ==> 所以1只能出去一次,1号节点出度为1
2.第n列除最后一个外有且有一个1==>所以n只能被其他点到一次,n号结点入度为1
3. 第2-n-1行的和等于其列的和 ==> 2-n-1号结点出度等于入度
除了最短路外,还要想到包含1的最小环+包含n的最小环也是满足上述情况的。这样才做完
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define inf 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int maxn = 3e2 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; int n; int G[305][305]; int vis[maxn],dist[maxn]; int spfa(int s,int e){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) dist[i]=inf; queue<int> q; if( s==e ){//找最小环 for(int i=1;i<=n;i++){ if( i!=s ){ q.push(i); vis[i]=1; dist[i]=G[s][i]; } } } else{ q.push(s); vis[s]=1; dist[s]=0; } while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=1;i<=n;i++){ if( dist[i]>dist[u]+G[u][i] ){ dist[i]=dist[u]+G[u][i]; if( !vis[i] ){ q.push(i); vis[i]=1; } } } } return dist[e]; } int main(){ while( scanf("%d",&n)!=EOF ){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%d",&G[i][j]); } } printf("%d ",min(spfa(1,n),spfa(1,1)+spfa(n,n))); } return 0; }
【POJ 3169 Layout】
这题跟之前的candies有点像,也是差分约束。
首先看它有两个约束条件ML和MD,那就很烦,我们可以一个一个考虑。ML代表的是A,B最远离多少,那么如果只有ML这一个约束的话,题目就跟candies一模一样了;这个时候再考虑MD,表示A,B最近隔多远;由于我们想维护的数组dist[i]代表1与i之间的最远距离,然后如果原来知道了dist[B]的话,那么我们可以推出来1到A的最远距离是dist[B]-D,所以从B到A建一条权值为-D的边就行了。
最后考虑如果有负环的话那就是no possible layout,因为负环代表有一个点dist[i]<0,现实意义是离1最远差一个负数,明显说不通;
如果没有路到dist[n]那就是多远都可以,因为没有有效约束能约束到终点。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define inf 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int maxn = 1e3 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; int n,m1,m2,cnt,head[maxn],s,t; struct edge{ int v,next,d; }edges[30010]; void addedge(int u,int v,int d){ cnt++; edges[cnt].v=v; edges[cnt].next=head[u]; edges[cnt].d=d; head[u]=cnt; } int vis[maxn],dist[maxn],update[maxn]; bool spfa(){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) dist[i]=inf; memset(update,0,sizeof(update)); queue<int> q; q.push(s); vis[s]=1; dist[s]=0; while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if( dist[v]>dist[u]+edges[i].d ){ dist[v]=dist[u]+edges[i].d; if( !vis[v] ){ q.push(v); update[v]++; if( update[v]>=n ) return true; } } } } return false; } int main(){ while( scanf("%d%d%d",&n,&m1,&m2)!=EOF ){ cnt=0; memset(head,0,sizeof(head)); int u,v,c; for(int i=1;i<=m1;i++){ scanf("%d%d%d",&u,&v,&c); addedge(u,v,c); } for(int i=1;i<=m2;i++){ scanf("%d%d%d",&u,&v,&c); addedge(v,u,-c); } s=1; t=n; //有负环-1,到不了t -2,其他dist[t] if( spfa() ) printf("-1 "); else if( dist[t]==inf ) printf("-2 "); else printf("%d ",dist[t]); } return 0; }
最后总结一下最短路专题,感觉其本质是dfs+剪枝,因为松弛时用的dist数组实际上就是过程性剪枝的一种。也正因为如此,所以spfa,dij模板可以维护出除最短路外的很多东西。由于spfa很方便,在我心目中前面的题把spfa送上神坛,但由于该算法的不稳定,后面的题又把其打下神坛。。。然后是最短路的一些变种,如分层/等级限制/差分。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define inf 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int maxn = 1e3 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; int n,m1,m2,cnt,head[maxn],s,t; struct edge{ int v,next,d; }edges[30010]; void addedge(int u,int v,int d){ cnt++; edges[cnt].v=v; edges[cnt].next=head[u]; edges[cnt].d=d; head[u]=cnt; } int vis[maxn],dist[maxn],update[maxn]; bool spfa(){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) dist[i]=inf; memset(update,0,sizeof(update)); queue<int> q; q.push(s); vis[s]=1; dist[s]=0; while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if( dist[v]>dist[u]+edges[i].d ){ dist[v]=dist[u]+edges[i].d; if( !vis[v] ){ q.push(v); update[v]++; if( update[v]>=n ) return true; } } } } return false; } int main(){ while( scanf("%d%d%d",&n,&m1,&m2)!=EOF ){ cnt=0; memset(head,0,sizeof(head)); int u,v,c; for(int i=1;i<=m1;i++){ scanf("%d%d%d",&u,&v,&c); addedge(u,v,c); } for(int i=1;i<=m2;i++){ scanf("%d%d%d",&u,&v,&c); addedge(v,u,-c); } s=1; t=n; //有负环-1,到不了t -2,其他dist[t] if( spfa() ) printf("-1 "); else if( dist[t]==inf ) printf("-2 "); else printf("%d ",dist[t]); } r