zoukankan      html  css  js  c++  java
  • 算法竞赛入门经典 写题笔记(第五章 图论算法与模型2)

    本节内容——

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

    例题9 飞机调度

    有n架飞机需要着陆。每架飞机都可以选择“早着陆"和”晚着陆“两种方式之一,且必须选择一种。第i架飞机的早着陆时间为(E_i),晚着陆时间为(L_I),不得在其他时间着陆。现在需要安排这些飞机的着陆方式,使得整个着陆计划尽量安全。换句话说,如果把所有飞机的实际着陆时间按照从早到晚的顺序排列,相邻两个着陆时间间隔的最小值(成为安全间隔)尽量大。

    也就是二分这个时间,然后判断该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_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
    ",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
    ",node[u].r,node[v].l);
                add(node[v].r,node[u].l),printf("%d %d
    ",node[v].r,node[u].l);
            }
            else
            {
                add(node[u].l,node[v].r),printf("%d %d
    ",node[u].l,node[v].r);
                add(node[u].r,node[v].l),printf("%d %d
    ",node[u].r,node[v].l);
                add(node[v].l,node[u].r),printf("%d %d
    ",node[v].l,node[u].r);
                add(node[v].r,node[u].l),printf("%d %d
    ",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
    ",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
    ",op[node[i].l]);
            else if(done[node[i].l]==0&&done[node[i].r]==0) printf("%c
    ",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
    ",op[node[i].l],op[node[i].r]);
            if(check()==false) {printf("No solution.
    ");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 机场快线

    机场快线分为经济线和商业线两种,线路、速度和价钱都不同。现在你有一张商业线的车票,可以坐一站商业线,而其他时候只能乘坐经济线。假设换成时间忽略不计,你的任务是找一条取机场最快的线路,然后输出方案。(保证最优解唯一)

    因为商业线只能坐一站,而且数据范围在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_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("
    ");
            else ++kase;
            if(s > 0) {
                print(s, p1, 1, S); printf(" ");
                print(t, p2, 2, T); printf("
    ");
                printf("%d
    ",s);
            }
            else {
                print(T, p1, 1, S); printf("
    ");
                printf("Ticket Not Used
    ");
            }
            printf("%d
    ",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_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
    ",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
    ",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
    ",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.
    ");
            else
            {
                while(l+eps<r)
                {
                    double mid=(l+r)/2.0;
                    // printf("l=%.2lf r=%.2lf mid=%.2lf
    ",l,r,mid);
                    if(check(mid)) r=mid;
                    else l=mid;
                }
                printf("%.2lf
    ",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
    ");continue;}
            else if(check(r)) {printf("Infinite
    ");continue;}
            while(l<=r)                                                                  
            {
                int mid=(l+r)>>1;
                if(check(mid)) ans=mid,l=mid+1;
                else r=mid-1;
            }
            printf("%d
    ",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
    ",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
    ");
            else printf("%d
    ",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("
    ");
    	}
    }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
    ",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
    ",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,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
    ",++kase,dis[T]);
            // printf("%d
    ",dis[T]);
        }
        return 0;
    }
    
  • 相关阅读:
    Collections与Arrays
    TreeMap
    HashMap
    单列集合的体系
    泛型的上下限
    09年最受关注的十大Visual Studio插件
    编码中的硬回车与软回车
    无法打开包括文件:'atlrx.h'的解决办法[原]
    【转】Notepad++,Eclipse和Visual Studio 2005常用快捷键对比
    【转】printf格式控制(你所不知道的printf妙用)
  • 原文地址:https://www.cnblogs.com/fengxunling/p/10851094.html
Copyright © 2011-2022 走看看