线性规划与网络流24题の4 魔术球问题(最小路径覆盖)

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

对于给定的 n,计算在 n 根柱子上最多能放多少个球。

http://wikioi.com/problem/1234/


最小路径覆盖:找最少的路径,覆盖图中所有的点,每个点只存在于一条路径中。

最小路径覆盖 = 二分图一侧的顶点数 - 最大匹配(最大流)

可以这样理解,当图中没有路径的时候,最小路径=顶点数 - 0,每增加一个匹配,路径就要减少1。


这道题就是将每个数拆成二分图中的两个点i 和 i‘,从源点向i连一条边,从i'向汇点连一条边。如果i+j=完全平方数,就从i向j'连一条边。

每加一个数求一次最小路径,每一次最小路径 = n都是一个可行解,求最大的可行解。


AC代码:每次都重新求最大流(因为oj貌似没有special judge =。=)

//#pragma comment(linker,"/STACK:1024000000,1024000000")
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef double DB;
typedef long long ll;
typedef pair<int, int> PII;

#define pb push_back
#define MP make_pair
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1

const DB eps = 1e-6;
const int inf = ~0U>>1;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const int maxn = 40000 + 10;


///init是初始化要在加边之前初始化,然后调用max_flow(顶点数,边数,源点, 汇点)
const int maxv = 40000 + 10;///顶点个数
const int maxe = 1000000 + 10;///边数

int c[maxe];

struct node{
    int v, cap, next;
}edge[maxe];
int head[maxv], cnt;
int n;///n是节点个数,m是边数
int st, ed;///st是源点,ed是汇点
int gap[maxv], h[maxv];
void addedge(int u, int v, ll w){///有向图加边
    edge[cnt].v = v; edge[cnt].cap = w; edge[cnt].next = head[u]; head[u] = cnt++;///正向边
    edge[cnt].v = u; edge[cnt].cap = 0; edge[cnt].next = head[v]; head[v] = cnt++;///反向边
}
int dfs(int x, int cost){
    if(x == ed) return cost; ///当前节点是汇点,直接返回cost

    int can = cost, d, minh = n - 1;
    for(int i=head[x]; ~i; i=edge[i].next){
        int v = edge[i].v, w = edge[i].cap;
        if(w > 0){///如果这条边的容量大于0
            if(h[v] + 1 == h[x]){///如果这是允许弧
                if(can > w) d = w;///如果当前弧的容量小于之前可增广的容量
                else d = can;

                d = dfs(v, d);///从v开始可增广的容量为d

                ///更新弧的容量和可增广的容量
                edge[i].cap -= d;
                edge[i ^ 1].cap += d;
                can -= d;

                if(h[st] >= n) return cost - can;
                if(!can) break;///不能再继续增广
            }
            if(h[v] < minh) minh = h[v];///更新最小标号
        }
    }
    if(can == cost){///如果没有增广...GAP
        gap[h[x]]--;
        if(gap[h[x]] == 0) h[st] = n;///存在断层,没有增广路了
        h[x] = minh + 1;///重新标记,保证下次再访问的时候有流量
        gap[h[x]]++;
    }
    return cost - can;///在这个点之前可以增广的 - 访问这个点之后可以增广的 = 在这个点增广的容量
}
int max_flow(int N){///SAP+GAP优化
    n = N;//m = M;
    for(int i=0; i<cnt; i++) c[i] = edge[i].cap;
    memset(h, 0, sizeof(h));///h[i]表示i节点的标号
    memset(gap, 0, sizeof(gap));///gap[i]表示标号为i的节点个数
    gap[0] = n;///初始有n个节点标号为0
    int ret = 0;
    while(h[st] < n){
        ret += dfs(st, inf);
    }
    return ret;
}
void init(int source, int sink){
    memset(head, -1, sizeof(head)); cnt = 0;
    st = source; ed = sink;
}
bool vis[maxn];
void Dfs(int x){
    vis[x] = true;
    for(int i=head[x]; ~i; i=edge[i].next){
        if(edge[i].cap > 0 && !vis[edge[i].v]) Dfs(edge[i].v);
    }
}
int used[maxn];
void Find(int x, int idx){
    used[x] = idx;
    int v;
    bool flag = false;
    for(int i=head[x << 1]; ~i; i=edge[i].next){
        v = edge[i].v;
        if(edge[i].cap == 0 && v != st){flag = true; break;}
    }
    if(flag) Find(v >> 1, idx);
}
int N;
int main(){
    cin >> N;
    int num = 1;
    init(0, 1);
    int ans = 0, tot;
    while(1){
        addedge(st, num << 1, 1);
        addedge(num << 1 | 1, ed, 1);
        for(int i=1; i<num; i++)
        if(floor(sqrt((DB)i + num)) == ceil(sqrt((DB)i + num)))
            addedge(i << 1, num << 1 | 1, 1);
        ans = max_flow((num << 1) + 2);
        if(num - ans == N){
           memset(used, -1, sizeof(used));
            tot = 0;
            for(int i=1; i<=num; i++)
                if(used[i] == -1) Find(i, tot++);
        }
        if(num - ans > N) break;
        num ++;
        for(int i=0; i<cnt; i++) edge[i].cap = c[i];
    }
    cout << --num << endl;

    for(int i=0; i<tot; i++){
        bool flag =  false;
        for(int j=1; j<=n; j++)
        if(used[j] == i){
            if(!flag){printf("%d", j); flag = true;}
            else printf(" %d", j);
        }
        puts("");
    }
    return 0;
}


每次在残余网络中求最大流的代码

//#pragma comment(linker,"/STACK:1024000000,1024000000")
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef double DB;
typedef long long ll;
typedef pair<int, int> PII;

#define pb push_back
#define MP make_pair
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1

const DB eps = 1e-6;
const int inf = ~0U>>1;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const int maxn = 40000 + 10;


///init是初始化要在加边之前初始化,然后调用max_flow(顶点数,边数,源点, 汇点)
const int maxv = 40000 + 10;///顶点个数
const int maxe = 1000000 + 10;///边数

int c[maxe];

struct node{
    int v, cap, next;
}edge[maxe];
int head[maxv], cnt;
int n;///n是节点个数,m是边数
int st, ed;///st是源点,ed是汇点
int gap[maxv], h[maxv];
void addedge(int u, int v, ll w){///有向图加边
    edge[cnt].v = v; edge[cnt].cap = w; edge[cnt].next = head[u]; head[u] = cnt++;///正向边
    edge[cnt].v = u; edge[cnt].cap = 0; edge[cnt].next = head[v]; head[v] = cnt++;///反向边
}
int dfs(int x, int cost){
    if(x == ed) return cost; ///当前节点是汇点,直接返回cost

    int can = cost, d, minh = n - 1;
    for(int i=head[x]; ~i; i=edge[i].next){
        int v = edge[i].v, w = edge[i].cap;
        if(w > 0){///如果这条边的容量大于0
            if(h[v] + 1 == h[x]){///如果这是允许弧
                if(can > w) d = w;///如果当前弧的容量小于之前可增广的容量
                else d = can;

                d = dfs(v, d);///从v开始可增广的容量为d

                ///更新弧的容量和可增广的容量
                edge[i].cap -= d;
                edge[i ^ 1].cap += d;
                can -= d;

                if(h[st] >= n) return cost - can;
                if(!can) break;///不能再继续增广
            }
            if(h[v] < minh) minh = h[v];///更新最小标号
        }
    }
    if(can == cost){///如果没有增广...GAP
        gap[h[x]]--;
        if(gap[h[x]] == 0) h[st] = n;///存在断层,没有增广路了
        h[x] = minh + 1;///重新标记,保证下次再访问的时候有流量
        gap[h[x]]++;
    }
    return cost - can;///在这个点之前可以增广的 - 访问这个点之后可以增广的 = 在这个点增广的容量
}
int max_flow(int N){///SAP+GAP优化
    n = N;//m = M;
    memset(h, 0, sizeof(h));///h[i]表示i节点的标号
    memset(gap, 0, sizeof(gap));///gap[i]表示标号为i的节点个数
    gap[0] = n;///初始有n个节点标号为0
    int ret = 0;
    while(h[st] < n){
        ret += dfs(st, inf);
    }
    return ret;
}
void init(int source, int sink){
    memset(head, -1, sizeof(head)); cnt = 0;
    st = source; ed = sink;
}
bool vis[maxn];
void Dfs(int x){
    vis[x] = true;
    for(int i=head[x]; ~i; i=edge[i].next){
        if(edge[i].cap > 0 && !vis[edge[i].v]) Dfs(edge[i].v);
    }
}
int used[maxn];
void Find(int x, int idx){
    used[x] = idx;
    int v;
    bool flag = false;
    for(int i=head[x << 1]; ~i; i=edge[i].next){
        v = edge[i].v;
        if(edge[i].cap == 0 && v != st){flag = true; break;}
    }
    if(flag) Find(v >> 1, idx);
}
int N;
int main(){
    cin >> N;
    int num = 1;
    init(0, 1);
    int ans = 0;
    while(1){
        addedge(st, num << 1, 1);
        addedge(num << 1 | 1, ed, 1);
        for(int i=1; i<num; i++)
        if(floor(sqrt((DB)i + num)) == ceil(sqrt((DB)i + num)))
            addedge(i << 1, num << 1 | 1, 1);
        ans += max_flow((num << 1) + 2);
        if(num - ans > N) break;
        num ++;
    }
    cout << --num << endl;
    memset(used, -1, sizeof(used));
    int tot = 0;
    for(int i=1; i<=num; i++)
        if(used[i] == -1) Find(i, tot++);
    for(int i=0; i<tot; i++){
        bool flag =  false;
        for(int j=1; j<=n; j++)
        if(used[j] == i){
            if(!flag){printf("%d", j); flag = true;}
            else printf(" %d", j);
        }
        puts("");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值