POWOJ 1739: 魔术球问题 DAG最小路径覆盖转最大流

Posted 掉血菜鸡煮熟中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POWOJ 1739: 魔术球问题 DAG最小路径覆盖转最大流相关的知识,希望对你有一定的参考价值。

1739: 魔术球问题

题意:

假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。 (1)每次只能在某根柱子的最上面放球。 (2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。 试设计一个算法,计算出在n根柱子上最多能放多少个球。对于给定的n,计算在n根柱子上最多能放多少个球。

tags: 对大佬来说应该是很素的一道题,但某还是花了好多时间才做出来。。 一开始连建图都有点懵,然后最小路径还是新概念,最大匹配也不太懂,最大流倒是会一点。 然后要打印答案,也不会,或者说最小路径该怎么从网络流中反映出来,这没怎么搞懂。

可以参考 百度文库,讲的很详细。

1、首先要想到怎么建图,然后把最小路径覆盖转化为二分图最大匹配,再化为最大流求解。

2、某是二分答案,每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数 。  对于i<j,且 i+j是完全平方数, 建二分图,即连接点 i 和点 m+j 。最后1~m点连接源点,m+1~2*m点连接汇点。   在二分图中求出最大匹配数,DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数。 最后搜索打印答案。

3、本来二分应该要比枚举快,但这题,枚举可以利用上次的残余网络,而二分每次要重新建图,反而更慢。

建模分析
由于是顺序放球,每根柱子上的球满足这样的特征,即下面的球编号小于上面球的编号。抽象成图论,把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数。每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖。

技术分享
//  1739: 魔术球问题
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,b,a) for (int i=b;i>=a;i--)
#define mes(a,b)  memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
typedef long long ll;
const int N = 100005;

int n, tot, dis[N], head[N], cur[N], remain[N<<1], path[N];
bool vis[N];
struct Edge{int to, next;}e[N<<1];
void Addedge(int u,int v,int w) {
    e[tot]={v,head[u]}; remain[tot]=w;  head[u]=tot++;
    e[tot]={u,head[v]}; remain[tot]=0;  head[v]=tot++;  //反向流量初始为 0
}

bool bfs(int st, int ed)    // bfs()查询 s到 t是否还有增广路径
{
    for(int i=st; i<=ed; i++) cur[i]=head[i];
    mes(dis, -1);
    queue<int > q;
    q.push(st);  dis[st]=0;
    while(!q.empty())
    {
        int u=q.front();  q.pop();
        for(int i=head[u]; i!=-1; i=e[i].next) {
            int to=e[i].to;
            if(dis[to]==-1 && remain[i]) {  //remain[]<0 也可
                q.push(to);
                dis[to]=dis[u]+1;
                if(to==ed) return true;
            }
        }
    }
    return false;
}
int dfs(int now, int ed, int flow)      // flow表示能够提供给当前结点的最大流量
{
    if(now==ed || flow==0) return flow;
    int s, ans=0;
    for(int &i=cur[now]; i!=-1; i=e[i].next) {  // &i=cur[now],改变了cur[now],让下一次的dfs不重复走
        int to=e[i].to;
        if(dis[to]==dis[now]+1 && (s=dfs(to,ed,min(flow,remain[i]))) ) {
            if(to>(ed-1)/2) path[now]=to-(ed-1)/2, vis[path[now]]=1;    // 记录路径
            ans+=s, flow-=s;            // ans表示当前结点可以消耗的最大流量
            remain[i]-=s, remain[i^1]+=s;   // 更新残余流量
            if(flow==0) break;
        }
    }
    return ans;
}
int dinic(int m)
{
    int st=0, ed=2*m+1, s, ans=0;
    while(bfs(st,ed))
        while(s=dfs(st,ed,INF))
            ans+=s;         // ans即是最大流,也是二分图最大匹配数
    return ans;
}
int solve(int m)
{
    int st=0, ed=2*m+1;
    tot=0;  mes(head,-1);   mes(vis,false);   mes(path, 0);
    rep(i,1,m)  rep(j,i+1,m) {        //加边,建二分图
        if((int)sqrt(i+j)==sqrt(i+j)) Addedge(i,m+j,1);
    }
    rep(i,1,m) {
        Addedge(st,i,1), Addedge(m+i,ed,1);
    }
    return m-dinic(m);      // DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数
    // 二分图中,最小边覆盖 = 图中点的个数 - 最大匹配数=最大独立集 , 最大匹配数 = 左边匹配点 + 右边未匹配点
}

void print(int ans)     // 打印答案
{
    printf("%d\n", ans);
    rep(i,1,ans) if(vis[i]==0) {
        printf("%d", i);
        int pos=i;
        while(path[pos]) { printf(" %d", path[pos]); pos=path[pos]; }
        puts("");
    }
}
int main()
{
    scanf("%d", &n);
    int l=n, r=2000, ans=0;
    while(l<=r) {
        int mid=(l+r)>>1;
        int ans1=solve(mid);
        if(ans1<=n) {    // 每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数
            if(ans1==n)  ans=max(ans, mid);
            l=mid+1;
        }
        else r=mid-1;
    }
    solve(ans);
    print(ans);

    return 0;
}
View Code

以上是关于POWOJ 1739: 魔术球问题 DAG最小路径覆盖转最大流的主要内容,如果未能解决你的问题,请参考以下文章

PowerOJ1739 魔术球问题

luogu P2765 魔术球问题

魔术球问题

「网络流24题」魔术球问题

网络流24题魔术球问题(最小不相交路径覆盖)

网络流24题魔术球问题(最大流)