算法竞赛入门经典 写题笔记(第五章 图论算法与模型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\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 机场快线

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

因为商业线只能坐一站,而且数据范围在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("\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_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;
}
posted @ 2019-05-12 10:31  风浔凌  阅读(184)  评论(0编辑  收藏  举报