本節內容——
- 2-SAT
- dijstra算法的一些應用
- SPFA算法的一些應用
例題9 飛機調度
有n架飛機需要著陸。每架飛機都可以選擇“早著陸"和”晚著陸“兩種方式之一,且必須選擇一種。第i架飛機的早著陸時間為\(E_i\),晚著陸時間為\(L_I\),不得在其他時間著陸?,F在需要安排這些飛機的著陸方式,使得整個著陸計劃盡量安全。換句話說,如果把所有飛機的實際著陸時間按照從早到晚的順序排列,相鄰兩個著陸時間間隔的最小值(成為安全間隔)盡量大。
也就是二分這個時間,然后判斷該2-SAT是否有解。(因為這個間隔時間越小就也可能有合法解,反之越不可能,所以可以二分答案)
構圖的時候,如果兩個決策(比如說\(i_l,j_l\))間隔小于二分的答案,我們就給\(i_l,j_r\)和\(i_r,j_l\)連有向邊。
然后跑tarjan判斷就行了,如果同一個飛機的兩個決策在一個強聯通分量里面,就沒有合法解了。
順便一提,劉汝佳書上寫的那個做法復雜度是假的qwq
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 4010
#define INF 0x3f3f3f3f
using namespace std;
int n,m,t,tot,cnt,top;
int head[MAXN],dfn[MAXN],low[MAXN],in[MAXN],st[MAXN],c[MAXN];
struct Edge{int nxt,to;}edge[MAXN*MAXN];
struct Node{int l,r;}node[MAXN];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void tarjan(int x)
{dfn[x]=low[x]=++tot;in[x]=1;st[++top]=x;for(int i=head[x];i;i=edge[i].nxt){int v=edge[i].to;if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);else if(in[v]) low[x]=min(low[x],dfn[v]);}if(dfn[x]==low[x]){int v;++cnt;do{v=st[top--],c[v]=cnt,in[v]=0;}while(x!=v);}
}
inline bool check(int x)
{memset(head,0,sizeof(head));t=tot=top=cnt=0;for(int i=1;i<=(n>>1);i++)for(int j=1;j<=(n>>1);j++){if(i==j) continue;int a=abs(node[i].l-node[j].l);int b=abs(node[i].l-node[j].r);int c=abs(node[i].r-node[j].l);int d=abs(node[i].r-node[j].r);if(a<x) add(i*2-1,j*2);if(b<x) add(i*2-1,j*2-1);if(c<x) add(i*2,j*2);if(d<x) add(i*2,j*2-1);}memset(low,0,sizeof(low));memset(dfn,0,sizeof(dfn));memset(in,0,sizeof(in));for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);for(int i=1;i<n;i+=2)if(c[i]==c[i+1])return false;return true;
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d",&n)!=EOF){for(int i=1;i<=n;i++) scanf("%d%d",&node[i].l,&node[i].r);n<<=1;int l=0,r=INF,ans=0;while(l<=r){int mid=(l+r)>>1;if(check(mid)) ans=mid,l=mid+1;else r=mid-1;}printf("%d\n",ans);}return 0;
}
例題10 宇航員分組
有A,B,C三種任務要分配給n個宇航員,其中每個宇航員恰好要分配一個任務。設所有n個宇航員的平均年齡為x,只有年齡大于或等于x的宇航員才能分配任務A;只有年齡嚴格小于x的宇航員才能分配任務B,而任務C沒有限制。有m對宇航員相互討厭,因此不能分配到統一任務。現在需要找出一個滿足上訴所有要求的任務分配方案。
3-SAT???不可能的。我們只要處理一下年齡,對于每個宇航員,照樣是2-SAT.
然后就......和上面那題一樣做就行了啊??
但是為什么會RE啊......搞不懂......先把代碼貼上,回來找鍋(咕咕咕)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 100010
using namespace std;
int n,m,all,kkk,cnt,tot,top,t;
int head[MAXN],done[MAXN];
int dfn[MAXN],low[MAXN],in[MAXN],st[MAXN],c[MAXN];
char op[MAXN];
struct Node{int l,r,age;}node[MAXN];
struct Edge{int nxt,to;}edge[MAXN<<1];
struct Line{int u,v;}line[MAXN<<1];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void tarjan(int x)
{low[x]=dfn[x]=++tot;for(int i=head[x];i;i=edge[i].nxt){int v=edge[i].to;if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);else if(in[v]) low[x]=min(low[x],dfn[v]);}if(low[x]==dfn[x]){int v;++cnt;do{v=st[top--];in[v]=0;c[v]=++cnt;}while(x!=v);}
}
inline bool check()
{memset(head,0,sizeof(head));memset(in,0,sizeof(in));memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));top=tot=t=cnt=0;for(int i=1;i<=m;i++){int u=line[i].u,v=line[i].v;if(node[u].age^node[v].age) {add(node[u].r,node[v].l),printf("%d %d\n",node[u].r,node[v].l);add(node[v].r,node[u].l),printf("%d %d\n",node[v].r,node[u].l);}else{add(node[u].l,node[v].r),printf("%d %d\n",node[u].l,node[v].r);add(node[u].r,node[v].l),printf("%d %d\n",node[u].r,node[v].l);add(node[v].l,node[u].r),printf("%d %d\n",node[v].l,node[u].r);add(node[v].r,node[u].l),printf("%d %d\n",node[v].r,node[u].l);}}for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i),cout<<i<<endl;for(int i=1;i<=n;i++)if(c[node[i].l]==c[node[i].r]&&c[node[i].l]!=0)return false;return true;
}
inline bool solve(int x,int c)
{done[x]=c,done[x^1]=3-c;printf("done[%d]=%d done[%d]=%d\n",x,done[x],x^1,done[x^1]);cout<<endl;for(int i=head[x];i;i=edge[i].nxt){int v=edge[i].to;if(done[v]&&done[v]==c) return false; else if(!done[v]) solve(v,c);}return true;
}
inline void print()
{cout<<"yes"<<endl;for(int i=1;i<=n;i++){if(done[node[i].l]==1) printf("%c\n",op[node[i].l]);else if(done[node[i].l]==0&&done[node[i].r]==0) printf("%c\n",op[node[i].l]);}
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d%d",&n,&m)==2){if(n==0&&m==0) break;memset(head,0,sizeof(head));top=tot=t=cnt=all=0;for(int i=1;i<=n;i++) scanf("%d",&node[i].age),all+=node[i].age;all/=n;for(int i=1;i<=n;i++){if(node[i].age<all) node[i].age=0;else node[i].age=1;}for(int i=1;i<=n;i++) scanf("%d%d",&line[i].u,&line[i].v);for(int i=1;i<=n;i++) if(node[i].age==0) node[i].l=++kkk,op[kkk]='B',node[i].r=++kkk,op[kkk]='C';else node[i].l=++kkk,op[kkk]='A',node[i].r=++kkk,op[kkk]='C';for(int i=1;i<=n;i++)printf("%c %c\n",op[node[i].l],op[node[i].r]);if(check()==false) {printf("No solution.\n");continue;}memset(done,0,sizeof(done));bool flag=true;for(int i=1;i<=n;i++)if(!done[i])if(solve(node[i].l,1))flag=false;if(flag==true) {print();continue;}memset(done,0,sizeof(done));flag=true;for(int i=1;i<=n;i++)if(!done[i])solve(node[i].r,1);print();}return 0;
}
例題11 機場快線
機場快線分為經濟線和商業線兩種,線路、速度和價錢都不同?,F在你有一張商業線的車票,可以坐一站商業線,而其他時候只能乘坐經濟線。假設換成時間忽略不計,你的任務是找一條取機場最快的線路,然后輸出方案。(保證最優解唯一)
因為商業線只能坐一站,而且數據范圍在1000以內,所以我們可以枚舉坐的是哪一站。
假設我們用商業線車票從車站a坐到b,則從起點到a,從b到終點這兩部分的路線對于只存在經濟線的圖中一定是最短路。所以我們只需要從起點、終點開始做兩次最短路,記錄下從起點到每個點x的最短時間\(f(x)\)和它到終點的最短時間\(g(x)\),那么總時間就是\(f(a)+time(a,b)+g(b)\)。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod = 1000000000 + 7;
const int INF = 1000000000;
const int maxn = 500 + 10;
int T,n,m,S,d1[maxn], p1[maxn], d2[maxn], p2[maxn], vis[maxn], ok, dist;
struct node {int u, val;node(int u=0, int val=0):u(u), val(val) {}bool operator < (const node& rhs) const {return val > rhs.val;}
};
void print(int root, int p[], int id, int S) {vector<int> ans;while(root != S) {ans.push_back(root);root = p[root];}ans.push_back(root);int len = ans.size();if(id == 1) for(int i=len-1;i>=0;i--) {if(i != len-1) printf(" ");printf("%d",ans[i]);}else for(int i=0;i<len;i++) {if(i != 0) printf(" ");printf("%d",ans[i]);}
}
vector<node> g[maxn];
void BFS(int haha, int d[], int p[]) {priority_queue<node> q;q.push(node(haha, 0));for(int i=1;i<=n;i++) {d[i] = INF;}d[haha] = 0;memset(vis, false, sizeof(vis));while(!q.empty()) {node u = q.top(); q.pop();if(vis[u.u]) continue;vis[u.u] = true;int len = g[u.u].size();for(int i=0;i<len;i++) {node v = g[u.u][i];if(d[v.u] > d[u.u] + v.val) {d[v.u] = d[u.u] + v.val;p[v.u] = u.u;q.push(node(v.u, d[v.u]));}}}
}
int a,b,c,kase=0;
int main() {#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(~scanf("%d%d%d",&n,&S,&T)) {scanf("%d",&m);for(int i=1;i<=n;i++) g[i].clear();while(m--) {scanf("%d%d%d",&a,&b,&c);g[a].push_back(node(b, c));g[b].push_back(node(a, c));}scanf("%d",&m);ok = -1;BFS(S, d1, p1);BFS(T, d2, p2);int s = -1,t = -1,ans = d1[T], res = 0;for(int i=0;i<m;i++) {scanf("%d%d%d",&a,&b,&c);if(cur < ans) {ans = cur;s = b; t = a;}}if(kase) printf("\n");else ++kase;if(s > 0) {print(s, p1, 1, S); printf(" ");print(t, p2, 2, T); printf("\n");printf("%d\n",s);}else {print(T, p1, 1, S); printf("\n");printf("Ticket Not Used\n");}printf("%d\n",ans);}return 0;
}
書上還提到了dij算法的路徑統計,在這里就簡單說一下吧
枚舉兩點之間的所有最短路:先求出所有點到目標點的最短路長度\(d[i]\),然后從起點開始出發,只沿著\(d[i]=d[j]+dist(i,j)\)的邊走。
兩點之間的最短路計數:令\(f[i]\)表示從i到目標點的最短路的條數,則\(f[i]=\sum f[j] | d[i]=d[j]+dist(i,j)\)(這里書上寫錯了)
例題12 林中漫步
對于一張圖,只沿著滿足如下條件的道路(A,B)走:存在一條從B出發回家的路徑,比所有從A出發回家的路徑都短。問不同的回家路徑條數。
先跑完以家為源點的最短路,然后如果一個點a的最短路比b的小,那么連邊,這就是一個DAG了,然后再DP計個數就行了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 100010
using namespace std;
int n,m,t;
int head[MAXN<<1],done[MAXN],dis[MAXN],dp[MAXN];
struct Node
{ int u,d;friend bool operator < (Node x,Node y){return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
struct Line{int u,v,w;}line[MAXN<<1];
inline void add(int from,int to,int dis)
{edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline void dij(int x)
{priority_queue<Node>q;memset(done,0,sizeof(done));memset(dis,0x3f,sizeof(dis));q.push((Node){x,0});dis[x]=0;while(!q.empty()){int u=q.top().u;q.pop();if(done[u]) continue;done[u]=1;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(dis[v]>dis[u]+edge[i].dis)dis[v]=dis[u]+edge[i].dis,q.push((Node){v,dis[v]});}}
}
inline int solve(int x)
{if(x==2) return dp[x]=1;int cur_ans=0;for(int i=head[x];i;i=edge[i].nxt){int v=edge[i].to;if(!dp[v]) dp[v]=solve(v);cur_ans+=dp[v];}return cur_ans;
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d%d",&n,&m)==2&&n){memset(head,0,sizeof(head));memset(dp,0,sizeof(dp));t=0;for(int i=1;i<=m;i++){scanf("%d%d%d",&line[i].u,&line[i].v,&line[i].w);add(line[i].u,line[i].v,line[i].w);add(line[i].v,line[i].u,line[i].w);}dij(2);// for(int i=1;i<=n;i++) printf("dis[%d]=%d\n",i,dis[i]);memset(head,0,sizeof(head));t=0;for(int i=1;i<=m;i++){if(dis[line[i].u]>dis[line[i].v]) add(line[i].u,line[i].v,line[i].w);if(dis[line[i].v]>dis[line[i].u]) add(line[i].v,line[i].u,line[i].w);}printf("%d\n",solve(1));}return 0;
}
最短路樹:用dij算法可以求出單元最短路樹,方法是在發現\(d[i]+w(i,j)<d[j]\)時除了更新\(d[j]\)之外還要設置\(p[j]=i\)。這樣,所有點就形成了一棵樹。
要從起點出發沿著最短路走到其他任意點,只需要順著樹上的邊走即可。
例題13 戰爭和物流
給出一個n個節點m條邊的無向圖(n<=100,m<=1000),每條邊上有一個正權。令c等于每對節點的最短路長度之和。要求刪除一條邊后使得新的c值c'最大。不聯通的兩點的最短路長度視為L。
在源點確定的情況下,只要最短路樹不被破壞,起點到所有點的距離都不會發生改變。換句話說,只有刪除最短路樹上的n-1條邊(中的任意條),最短路樹才需要重新計算。
這樣的話,我們對于每個源點,最多只需要求n次而不是m次最短路,時間復雜度為\(O(n^2mlogn)\)(dij算法的時間復雜度為\(O(mlogn)\))
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 2010
using namespace std;
int n,m,l,t=1;
long long ans1,ans2;
int head[MAXN<<1],pre[MAXN],ex[MAXN<<1][MAXN],done[MAXN];
long long dis[MAXN],dist[MAXN];
struct Node
{int u;long long d;friend bool operator < (Node x,Node y){return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
inline void add(int from,int to,long long dis)
{edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=dis,head[to]=t;
}
inline long long dij(int x,int op)
{priority_queue<Node>q;memset(dis,127,sizeof(dis));memset(done,0,sizeof(done));memset(pre,-1,sizeof(pre));q.push((Node){x,0});dis[x]=0;while(!q.empty()){int u=q.top().u;q.pop();if(done[u]) continue; for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if((i==op)||(i^1)==op) continue;if(dis[v]>dis[u]+edge[i].dis){dis[v]=dis[u]+edge[i].dis;q.push((Node){v,dis[v]});pre[v]=i;}}}for(int i=1;i<=n;i++)if(pre[i]!=-1&&op==0) ex[pre[i]][x]=ex[pre[i]^1][x]=1;long long cur_ans=0;for(int i=1;i<=n;i++){if(dis[i]<1e17) cur_ans+=dis[i];else cur_ans+=l;}return cur_ans;
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);freopen("ce.out","w",stdout);#endifwhile(scanf("%d%d%d",&n,&m,&l)==3){memset(head,0,sizeof(head));memset(ex,0,sizeof(ex));t=1;ans1=0,ans2=0;for(int i=1;i<=m;i++){int x,y;long long w;scanf("%d%d%lld",&x,&y,&w);add(x,y,w);}for(int i=1;i<=n;i++)dist[i]=dij(i,0),ans1+=dist[i];for(int i=2;i<=t;i+=2){long long cur_ans=0;for(int j=1;j<=n;j++){if(ex[i][j]) cur_ans+=dij(j,i);else cur_ans+=dist[j];}ans2=max(ans2,cur_ans);}printf("%lld %lld\n",ans1,ans2);}return 0;
}
例題14 過路費(加強版)
運送貨物需要繳納過路費。進入一個尋裝需要繳納一個單位的貨物,進入一個城鎮時,每20個單位的貨物中就要上繳1個單位(比如,攜帶70個單位的貨物進入城鎮,需要繳納4個單位的貨物)。現在給定你一張圖,請找出一條繳納過路費最少的路線,并輸出。如果有多條路線,輸出字典序最小的。
逆推的dij算法。令\(d[i]\)表示進入節點i之后,至少需要\(d[i]\)個單位的貨物,到達目的地時貨物數量才足夠。則每次選擇一個\(d[i]\)最小的未標號的節點,更新它的所有前驅節點的d值就行了。輸出路徑的時候根據d值就可以構造出字典序最小的解。
emmmm為什么我把書上的解釋全抄下來了......因為我覺得寫得確實比較清晰啊qwq
這個題我的代碼還是不知道為什么鍋了。。。先敷衍一下放個maomao的吧......
#include <bits/stdc++.h>
using namespace std;#define int long longconst int N = 200 + 5;
const int INF = 0x3f3f3f3f3f3f3f3f;vector <int> G[N];int n, p, kase, dis[N], done[N]; char s, t;int get_tot (int w) {int l = 0, r = 1e17;while (l < r) {int mid = (l + r) >> 1;if (mid - ceil ((1.0 * mid) / 20.0) >= w) {r = mid;} else {l = mid + 1;}}return r;
}struct HeapNode {int u, d;bool operator < (HeapNode rhs) const {return d > rhs.d; }
};priority_queue <HeapNode> q;void solve () {memset (done, 0, sizeof (done));memset (dis, 0x3f, sizeof (dis));dis[(int)t] = isupper (t) ? get_tot (p) : p + 1;q.push ((HeapNode) {t, dis[(int)t]});while (!q.empty ()) {HeapNode now = q.top (); q.pop ();if (done[now.u]) continue;for (int i = 0; i < (int) G[now.u].size (); ++i) {int v = G[now.u][i];int _w = isupper (v) ? get_tot (dis[now.u]) : dis[now.u] + 1;if (dis[v] > _w) {dis[v] = _w;q.push ((HeapNode) {v, dis[v]});} }done[now.u] = true; }
}bool cmp (int lhs, int rhs) {return dis[lhs] == dis[rhs] ? lhs < rhs : dis[lhs] < dis[rhs];
}signed main () {#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile (cin >> n) {if (n == -1) break;cout << "Case " << ++kase << ":" << endl;for (int i = 0; i < N; ++i) G[i].clear ();for (int i = 1; i <= n; ++i) {static char u, v;cin >> u >> v;G[(int)u].push_back ((int)v);G[(int)v].push_back ((int)u);}cin >> p >> s >> t;solve ();int min_dis = 0x7fffffffffffffffll;for (int i = 0; i < (int)G[(int)s].size (); ++i) {int v = G[(int)s][i];min_dis = min (min_dis, dis[v]);}for (int i = 0; i < N; ++i) {if (!G[i].empty ()) {sort (G[i].begin (), G[i].end (), cmp);}}if (s == t) {cout << p << endl;cout << (char) t << endl;continue;}cout << min_dis << endl;int now = s; cout << (char) s << "-";while (now != t) {now = G[now][0];cout << (char) now; if (now != t) cout << "-";} cout << endl;}
}
例題15 在環中
給定一個n個點m條邊(n<=50)的加權有向圖,求平均權值最小的回路。輸入沒有自環。
二分答案。假設存在一個包含k條邊的回路,回路上各條邊的權值為\(w_1,w_2,...,w_k\),那么平均值小于mid意味著\((w_1-mid)+(w_2-mid)+...+(w_k-mid)<0\),那么就直接給每條邊減去mid,然后用SPFA判斷一下是否有負環就行了。
注意小數二分的時候精度不要取太高......常數太大會T的.......
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 110
#define eps 1e-7
using namespace std;
int n,m,t,T,kase;
int head[MAXN],done[MAXN],cnt[MAXN];
double dis[MAXN];
struct Line{int u,v;double w;}line[MAXN*MAXN];
struct Edge{int nxt,to;double dis;}edge[MAXN*MAXN];
inline void add(int from,int to,double dis)
{edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline bool spfa(int x)
{queue<int>q;for(int i=0;i<=n;i++) dis[i]=1e9,done[i]=cnt[i]=0;q.push(x);done[x]=1;dis[x]=0;while(!q.empty()){int u=q.front();q.pop();done[u]=0;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(dis[v]>dis[u]+edge[i].dis){dis[v]=dis[u]+edge[i].dis;if(!done[v]){done[v]=1;q.push(v);if(++cnt[v]>=n) return false; }}}}return true;
}
inline bool check(double x)
{bool flag=false;for(int i=1;i<=n;i++)for(int j=head[i];j;j=edge[j].nxt)edge[j].dis-=x;for(int i=1;i<=n;i++)if(!spfa(i)==true) flag=true;for(int i=1;i<=n;i++)for(int j=head[i];j;j=edge[j].nxt)edge[j].dis+=x;// puts("oh no");return flag;
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifscanf("%d",&T);while(T--){memset(head,0,sizeof(head));t=0;scanf("%d%d",&n,&m);double l=1e9,r=0.0;for(int i=1;i<=m;i++){scanf("%d%d%lf",&line[i].u,&line[i].v,&line[i].w);add(line[i].u,line[i].v,line[i].w);r=max(r,line[i].w);l=min(l,line[i].w);}printf("Case #%d: ",++kase);if(!check(r+1)) printf("No cycle found.\n");else{while(l+eps<r){double mid=(l+r)/2.0;// printf("l=%.2lf r=%.2lf mid=%.2lf\n",l,r,mid);if(check(mid)) r=mid;else l=mid;}printf("%.2lf\n",r);}}return 0;
}
例題16 Halum操作
給定一個有向圖,每條邊都有一個權值。每次你可以選擇一個節點v和一個整數d,把所有以v為終點的邊的權值減小d,把所有以v為起點的邊的權值增加d,最后要讓所有邊權的最小值為正且盡量大。
因為不同的操作互不影響,所以可以按照任意順序實施這些操作。另外,對于同一個節點的多次操作也可以合并,所以我們可以令sum(u)表示作用于節點u之上的所有d之和。然后二分答案x,問題轉化成為是否可以讓所有操作完成后每條邊的權值均不小于x。對于邊a->b,操作完之后應該是\(w(a,b)+sum[a]-sum[b] \ge x\),也就是\(sum[b]-sum[a]<=w(a,b)-x\)。這樣,就是一個差分約束系統了,我們連a到b,權值為\(w(a,b)-x\)的邊。然后用負環來判斷這個差分約束系統是否有解。
對了,書上的翻譯感覺有問題。依AC代碼來看,應該是讓所有邊的最小值為正且盡量大......
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 510
using namespace std;
int n,m,t;
int head[MAXN],done[MAXN],dis[MAXN],cnt[MAXN];
struct Edge{int nxt,to,dis;}edge[3000];
struct Line{int u,v,w;}line[3000];
inline void add(int from,int to,int dis)
{edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline bool spfa()
{queue<int>q;for(int i=1;i<=n;i++)q.push(i),done[i]=1,dis[i]=0x3f3f3f3f,cnt[i]=0;while(!q.empty()){int u=q.front();q.pop();done[u]=0;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(dis[v]>dis[u]+edge[i].dis){dis[v]=dis[u]+edge[i].dis;if(!done[v]){done[v]=1;cnt[v]++;q.push(v);if(cnt[v]>=n) return false;}}}}return true;
}
inline bool check(int x)
{bool flag=false;for(int i=1;i<=n;i++)for(int j=head[i];j;j=edge[j].nxt)edge[j].dis-=x;if(spfa()) flag=true;for(int i=1;i<=n;i++)for(int j=head[i];j;j=edge[j].nxt)edge[j].dis+=x;return flag;
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d%d",&n,&m)!=EOF){memset(head,0,sizeof(head));t=0;int l=0,r=-0x3f3f3f3f,ans;for(int i=1;i<=m;i++){scanf("%d%d%d",&line[i].u,&line[i].v,&line[i].w);add(line[i].u,line[i].v,line[i].w);r=max(r,line[i].w);}if(!check(1)) {printf("No Solution\n");continue;}else if(check(r)) {printf("Infinite\n");continue;}while(l<=r) {int mid=(l+r)>>1;if(check(mid)) ans=mid,l=mid+1;else r=mid-1;}printf("%d\n",ans);}return 0;
}
例題17 蒸汽式壓路機
翻譯還是去看書吧.......(反正我大概也是每次都抄一遍)
拆點的最短路,因為一個位置表示的狀態不一樣,所以我們要拆點來分別代表這些狀態
把每個點\((r,c)\)拆成8個點\((r,c,dir,double)\),分別表示上一步從上下左右的哪個方向(dir)移動到這個點,以及移動到這個點的這條邊的權值是否已經加倍(doubled)
題解也還是去看書吧.......(反正圖我也是畫不出來的......)
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
#define MAXN 100010
#define INF 0x3f3f3f3f
using namespace std;
inline int read()
{int f=1,x=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48); ch=getchar();}return x;
}
int n,R,C,r1,c1,r2,c2,kase,t;
int inv[4]={2,3,0,1},dr[4]={-1,0,1,0},dc[4]={0,-1,0,1};
int grid[110][110][4],id[110][110][4][2];
int dis[MAXN],done[MAXN],head[MAXN];
const int Up=0;
const int Left=1;
const int Down=2;
const int Right=3;
struct Edge{int nxt,to,dis;}edge[20000010];
struct Node
{int u,d;friend bool operator < (struct Node x,struct Node y){return x.d>y.d;}
};
inline void add(int from,int to,int dis)
{// printf("[%d %d] %d\n",from,to,dis);edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline int ID(int r,int c,int dir,int doubled)
{int &x=id[r][c][dir][doubled];if(x==0) x=++n;return x;
}
inline bool cango(int r,int c,int dir)
{if(r<1||r>R||c<1||c>C) return false;return grid[r][c][dir]>0;
}
inline void dij(int s)
{priority_queue<Node>q;memset(done,0,sizeof(done));memset(dis,0x3f,sizeof(dis));dis[s]=0;q.push((Node){s,0});while(!q.empty()){int u=q.top().u;q.pop();if(done[u]) continue;done[u]=1;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(dis[v]>dis[u]+edge[i].dis){dis[v]=dis[u]+edge[i].dis;q.push((Node){v,dis[v]});}}}
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d%d%d%d%d%d",&R,&C,&r1,&c1,&r2,&c2)==6){if(R==0&&C==0) break;memset(head,0,sizeof(head));t=0;for(int r=1;r<=R;r++){for(int c=1;c<=C-1;c++)grid[r][c][Right]=grid[r][c+1][Left]=read();if(r!=R)for(int c=1;c<=C;c++)grid[r][c][Down]=grid[r+1][c][Up]=read();}n=0;memset(id,0,sizeof(id));for(int dir=0;dir<4;dir++)if(cango(r1,c1,dir))add(0,ID(r1+dr[dir],c1+dc[dir],dir,1),grid[r1][c1][dir]*2);for(int r=1;r<=R;r++)for(int c=1;c<=C;c++)for(int dir=0;dir<4;dir++) if(cango(r,c,inv[dir]))for(int newdir=0;newdir<4;newdir++) if(cango(r,c,newdir))for(int doubled=0;doubled<2;doubled++){int newr=r+dr[newdir];int newc=c+dc[newdir];int v=grid[r][c][newdir],newdoubled=0;if(dir!=newdir){if(!doubled) v+=grid[r][c][inv[dir]];newdoubled=1;v+=grid[r][c][newdir];}add(ID(r,c,dir,doubled),ID(newr,newc,newdir,newdoubled),v);}dij(0);int ans=INF;for(int dir=0;dir<4;dir++) if(cango(r2,c2,inv[dir]))for(int doubled=0;doubled<2;doubled++){int v=dis[ID(r2,c2,dir,doubled)];if(!doubled) v+=grid[r2][c2][inv[dir]];ans=min(ans,v);}printf("Case %d: ",++kase);if(ans==INF) printf("Impossible\n");else printf("%d\n",ans);}return 0;
}
例題18 低價空中旅行
給你一些票,每張聯票上標明上面以此經過的站,以及本票的價錢。必須從頭開始坐,可以在中途任何一站下飛機,下飛機后票上繳,不可以再次使用本票。
現在給出一些行程單,問如何買票能使得總花費最小(同一種票能夠買多張)。輸入保證行程總是可行的,行程單上的城市必須按照順序到達,但是中間可以經過一些輔助城市。
輸入保證每組數據最多包含20種聯票和20個行程單,聯票或者行程單上的相鄰城市保證不同。票和行程單都從1開始編號。
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<stack>
#include<algorithm>
using namespace std;
const int maxn=4100;
const int inf=1e9;
struct HeapNode
{int d,u;bool operator<(const HeapNode& rhs)const{return d>rhs.d;}
};
struct Edge{int from,to,dist,id;};
struct Dijkstra
{int n,m;vector<Edge>edges;vector<int>G[maxn];bool done[maxn];int d[maxn],p[maxn]; void init(int n){this->n=n;for(int i=0;i<n;i++)G[i].clear();edges.clear();}void AddEdge(int from,int to,int dist,int id){edges.push_back((Edge){from,to,dist,id});m=edges.size();G[from].push_back(m-1);}void dij(int s){priority_queue<HeapNode>Q;for(int i=1;i<=n;i++)d[i]=inf;d[s]=0;memset(done,0,sizeof(done));Q.push((HeapNode){0,s});while(!Q.empty()){HeapNode x=Q.top();Q.pop();int u=x.u;if(done[u])continue;done[u]=true;for(int i=0;i<G[u].size();i++){Edge& e=edges[G[u][i]];if(d[e.to]>d[u]+e.dist){d[e.to]=d[u]+e.dist;p[e.to]=G[u][i];Q.push((HeapNode){d[e.to],e.to});}}}}void print(int s,int t){stack<int>stk;while(t!=s){stk.push(edges[p[t]].id);t=edges[p[t]].from;}while(!stk.empty()){printf(" %d",stk.top());stk.pop();}printf("\n");}
}solver;
int id[12][410],res,tot;
vector<int>ticket[21];
map<int,int>mp;
inline int ID(int x)
{if(!mp.count(x)) mp[x]=++tot;return mp[x];
}
inline int ID2(int x,int y)
{if(id[x][y]==0) id[x][y]=++res;return id[x][y];
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifint n,m,q,u,v,money,kase=0;while(~scanf("%d",&n)&&n){int cost[21];mp.clear();tot=0;for(int i=1;i<=n;i++){ticket[i].clear(); scanf("%d%d",&cost[i],&m);for(int j=1;j<=m;j++){scanf("%d",&u);ticket[i].push_back(ID(u));}}kase++;scanf("%d",&q);vector<int>line;for(int p=1;p<=q;p++){int num;line.clear();memset(id,0,sizeof(id));res=0;scanf("%d",&num);for(int i=1;i<=num;i++){scanf("%d",&u);line.push_back(ID(u));}solver.init(num*tot+1);for(int tic=1;tic<=n;tic++)for(int pos=1;pos<num;pos++){int next=pos;for(int i=1;i<ticket[tic].size();i++){if(ticket[tic][i]==line[next])next++;solver.AddEdge(ID2(pos,ticket[tic][0]),ID2(next,ticket[tic][i]),cost[tic],tic);if(next==num) break;}}solver.dij(ID2(1,line[0]));int ans=solver.d[ID2(num,line[num-1])];printf("Case %d, Trip %d: Cost = %d\n",kase,p,ans);printf(" Tickets used:");solver.print(ID2(1,line[0]),ID2(num,line[num-1]));}}
}
例題19 動物園大逃亡
平面圖轉對偶圖,用最短路求最小割。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define S 0
#define T tot+1
#define MAXN 5000010
using namespace std;
int n,m,t,cur,kase,tot;
int dis[MAXN],done[MAXN],head[MAXN],id[1010][1010][2];
struct Node
{int u,d;friend bool operator <(struct Node x,struct Node y){return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
inline void add(int from,int to,int dis)
{// printf("[%d %d] %d\n",from,to,dis);edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=dis,head[to]=t;
}
inline void dij()
{priority_queue<Node>q;memset(dis,0x3f,sizeof(dis));memset(done,0,sizeof(done));q.push((Node){S,0});dis[S]=0;while(!q.empty()){int u=q.top().u; q.pop();if(done[u]) continue;done[u]=1;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(dis[u]+edge[i].dis<dis[v])dis[v]=dis[u]+edge[i].dis,q.push((Node){v,dis[v]});}}
}
int main()
{#ifndef ONLINE_JUDGEfreopen("ce.in","r",stdin);#endifwhile(scanf("%d%d",&n,&m)==2&&n+m){memset(head,0,sizeof(head));t=tot=0;n--,m--;// printf("n=%d m=%d\n",n,m);for(int i=0;i<=n+1;i++)for(int j=0;j<=m+1;j++)for(int k=0;k<=1;k++)id[i][j][k]=++tot;for(int i=1;i<=n+1;i++){int x;for(int j=1;j<=m;j++){scanf("%d",&x);add(id[i][j][1],id[i-1][j][0],x);}}for(int i=1;i<=n;i++){int x;for(int j=1;j<=m+1;j++){scanf("%d",&x);add(id[i][j][0],id[i][j-1][1],x);}}for(int i=1;i<=n;i++){int x;for(int j=1;j<=m;j++){scanf("%d",&x);add(id[i][j][1],id[i][j][0],x);}}for(int i=1;i<=n;i++) add(S,id[i][0][1],0);for(int j=1;j<=m;j++) add(S,id[n+1][j][1],0);for(int j=1;j<=m;j++) add(id[0][j][0],T,0);for(int i=1;i<=n;i++) add(id[i][m+1][0],T,0);dij();printf("Case %d: Minimum = %d\n",++kase,dis[T]);// printf("%d\n",dis[T]);}return 0;
}
轉載于:https://www.cnblogs.com/fengxunling/p/10851094.html
總結
以上是生活随笔為你收集整理的算法竞赛入门经典 写题笔记(第五章 图论算法与模型2)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。