编程语言
首页 > 编程语言> > 算法竞赛入门经典 写题笔记(第五章 图论算法与模型2)

算法竞赛入门经典 写题笔记(第五章 图论算法与模型2)

作者:互联网

本节内容——


例题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条边的无向图,

例题14 过路费(加强版)

例题15 在环中

例题16 Halum操作

标签:图论,int,head,写题,算法,edge,MAXN,include,dis
来源: https://www.cnblogs.com/fengxunling/p/10851094.html