算法競賽入門經典 寫題筆記(第五章 圖論算法與模型2)

本節內容——node

  • 2-SAT
  • dijstra算法的一些應用
  • SPFA算法的一些應用

例題9 飛機調度

有n架飛機須要着陸。每架飛機均可以選擇「早着陸"和」晚着陸「兩種方式之一,且必須選擇一種。第i架飛機的早着陸時間爲\(E_i\),晚着陸時間爲\(L_I\),不得在其餘時間着陸。如今須要安排這些飛機的着陸方式,使得整個着陸計劃儘可能安全。換句話說,若是把全部飛機的實際着陸時間按照從早到晚的順序排列,相鄰兩個着陸時間間隔的最小值(成爲安全間隔)儘可能大。ios

也就是二分這個時間,而後判斷該2-SAT是否有解。(由於這個間隔時間越小就也可能有合法解,反之越不可能,因此能夠二分答案)
構圖的時候,若是兩個決策(好比說\(i_l,j_l\))間隔小於二分的答案,咱們就給\(i_l,j_r\)\(i_r,j_l\)連有向邊。
而後跑tarjan判斷就好了,若是同一個飛機的兩個決策在一個強聯通份量裏面,就沒有合法解了。
順便一提,劉汝佳書上寫的那個作法複雜度是假的qwqc++

#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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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 機場快線

機場快線分爲經濟線和商業線兩種,線路、速度和價錢都不一樣。如今你有一張商業線的車票,能夠坐一站商業線,而其餘時候只能乘坐經濟線。假設換成時間忽略不計,你的任務是找一條取機場最快的線路,而後輸出方案。(保證最優解惟一)this

由於商業線只能坐一站,並且數據範圍在1000之內,因此咱們能夠枚舉坐的是哪一站。
假設咱們用商業線車票從車站a坐到b,則從起點到a,從b到終點這兩部分的路線對於只存在經濟線的圖中必定是最短路。因此咱們只須要從起點、終點開始作兩次最短路,記錄下從起點到每一個點x的最短期\(f(x)\)和它到終點的最短期\(g(x)\),那麼總時間就是\(f(a)+time(a,b)+g(b)\)spa

#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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(~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)\)(這裏書上寫錯了).net

例題12 林中漫步

對於一張圖,只沿着知足以下條件的道路(A,B)走:存在一條從B出發回家的路徑,比全部從A出發回家的路徑都短。問不一樣的回家路徑條數。翻譯

先跑完以家爲源點的最短路,而後若是一個點a的最短路比b的小,那麼連邊,這就是一個DAG了,而後再DP計個數就好了。code

#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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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_JUDGE
    freopen("ce.in","r",stdin);
    freopen("ce.out","w",stdout);
    #endif
    while(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 long

const 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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while (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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    int 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_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    while(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;
}
相關文章
相關標籤/搜索