线性规划与网络流24题 04魔术球问题

拆点的另一个方法。。: 把这个点 i 拆成  i<<1 和 ( i<<1) +1; 因为上限无穷所以这样会比较方便点。。。然后就是枚举时候不要二分。。。那样会重新构图的。。。顺序枚举可以用到回退边的流量来恢复构建的图。。。然后结合给的讲解就可以写出来了。。。。

【问题分析】
枚举答案转化为判定性问题,然后最小路径覆盖,可以转化成二分图最大匹配,从而用最大流解决。
【建模方法】
枚举答案A,在图中建立节点1..A。如果对于i<j有i+j为一个完全平方数,连接一条有向边(i,j)。该图是有向无环图,求最小路径覆盖。如果刚好满足最小路径覆盖数等于N,那么A是一个可行解,在所有可行解中找到最大的A,即为最优解。
具体方法可以顺序枚举A的值,当最小路径覆盖数刚好大于N时终止,A-1就是最优解。
【建模分析】
由于是顺序放球,每根柱子上的球满足这样的特征,即下面的球编号小于上面球的编号。抽象成图论,把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数。每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖。
最小路径覆盖数随球的数量递增不递减,满足单调性,所以可以枚举答案(或二分答案),对于特定的答案求出最小路径覆盖数,一个可行解就是最小路径覆盖数等于N的答案,求出最大的可行解就是最优解。本问题更适合枚举答案而不是二分答案,因为如果顺序枚举答案,每次只需要在残量网络上增加新的节点和边,再增广一次即可。如果二分答案,就需要每次重新建图,大大增加了时间复杂度。


#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 1<<30
#define ext 2000
#define N 200000
#define M 4005
#define cc(m,v) memset(m,v,sizeof(m))
struct node{
    int u,v,f,next;
}edge[N];
int head[M],p,lev[M],cur[M];
int que[N],squ[M],vis[M];
void ainit(){
    p=0,cc(head,-1),cc(vis,0),vis[0]=vis[1]=1;
}
void addedge(int u,int v,int f){
    edge[p].u=u,edge[p].v=v,edge[p].f=f,edge[p].next=head[u],head[u]=p++;
    edge[p].u=v,edge[p].v=u,edge[p].f=0,edge[p].next=head[v],head[v]=p++;
}
bool bfs(int s,int t){
    int i,v,u,qin=0,qout=0;
    cc(lev,-1),lev[s]=0,que[qin++]=s;
    while(qout!=qin){
        u=que[qout++];
        for(i=head[u];i!=-1;i=edge[i].next)
            if(edge[i].f>0 && lev[v=edge[i].v]==-1){
                lev[v]=lev[u]+1,que[qin++]=v;
                if(v==t) return 1;
            }
    }
    return 0;
}
int dinic(int s,int t){
    int i,k,f,u,qin;
    int flow=0;
    while(bfs(s,t)){
        memcpy(cur,head,sizeof(head));
        u=s,qin=0;
        while(1){
            if(u==t){
                for(k=0,f=inf;k<qin;k++)
                    if(edge[que[k]].f < f) f=edge[que[i=k]].f;
                for(k=0;k<qin;k++)
                    edge[que[k]].f-=f,edge[que[k]^1].f+=f;
                flow+=f,u=edge[que[qin=i]].u;
            }
            for(i=cur[u];cur[u]!=-1;i=cur[u]=edge[cur[u]].next)
                if(edge[i].f>0 && lev[u]+1==lev[edge[i].v]) break;
            if(cur[u]!=-1)
                que[qin++]=cur[u],u=edge[cur[u]].v;
            else{
                if(qin==0) break;
                lev[u]=-1,u=edge[que[--qin]].u;
            }
        }
    }
    return flow;
}
void dfs(int u){
    int v;
    vis[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
        if(edge[i].f==0 && !vis[v=edge[i].v] && edge[i].v>1){
            printf(" %d",(v-1)>>1);
            dfs(v-1); return;
        }
}
int main(){
    int n,i,j,k,s,t,ans;
    cc(squ,0);
    for(i=1;i*i<M;i++)
        squ[i*i]=1;
    while(scanf("%d",&n)!=-1){
        ainit();
        s=0,t=1;
        for(i=1;i<M ;i++){
            addedge(s,i<<1,1),addedge((i<<1)+1,t,1);
            for(j=1;j<i;j++)  if(squ[j+i])
                addedge(j<<1,(i<<1)+1,1);
            for(k=0;k<p;k+=2)
                edge[k].f+=edge[k^1].f,edge[k^1].f=0;
            if(i-dinic(s,t)>n) break;
        }
        printf("%d\n",i-1);
        ainit();ans=i-1;
        for(i=1;i<=ans;i++){
            addedge(s,i<<1,1),addedge((i<<1)+1,t,1);
            for(j=1;j<i;j++)  if(squ[j+i])
                addedge(j<<1,(i<<1)+1,1);
        }
        dinic(s,t);
        for(j=1;j<=ans;j++)
            if(!vis[j<<1]){
                printf("%d",j);
                dfs(j<<1);
                printf("\n");
            }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值