HDU 5772 (最大权闭合图 最小割)

题目链接:点击这里

题意: 给出一个序列, 问子序列的最大收益. 收益=价值-花费, 花费是子序列里面某个数字的个数的函数, 收益是任意两个下标对应的函数.

题解建模:
首先将点分为3类

第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于 w[i][j]+w[j][i] (表示得分)

第二类:原串中的n个点每个点拆出一个点,第i个点权值为 a[s[i]] (表示要花费)

第三类:对于10种字符拆出10个点,每个点的权值为 (b[x]a[x])

那么我们可以得到一个关系图 ,对于第一类中的点 Pij ,如果想要选择 Pij ,你就必须要选中第二类中的点i和j,对于第二类中的点如果你想选中第i个点,其对应的字符 s[i] ,那么就必须选中第三类中 s[i] 对应的点,因为每个种类的点第一次选中时花费是b[s[i]],而第二类中花费都是 a[s[i]] ,一定要补上 b[s[i]]a[s[i]] ,而且只需要补上一次。

得到上面的关系图后然后就是普通的最大权闭合子图问题,直接求解即可。

#include <bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm maxn*10
#define INF 111111111111111

#define type long long
char str[105];
int n;
int s, t;
struct Edge
{
    int from, to,next;
    type cap,flow;
    void get(int u,int a,int b,type c,type d)
    {
        from = u; to = a; next = b; cap = c; flow = d;
    }
}edge[maxm];
int tol;
int head[maxn];
int gap[maxn],dep[maxn],pre[maxn],cur[maxn];
void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}

void add_edge(int u,int v,type w,type rw=0)
{ //cout << u << " " << v << " " << w << endl;
    edge[tol].get(u, v,head[u],w,0);head[u]=tol++;
    edge[tol].get(v, u,head[v],rw,0);head[v]=tol++;
}
type sap(int start,int end,int N)
{
    memset(gap,0,sizeof(gap));
    memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof(head));
    int u=start;
    pre[u]=-1;
    gap[0]=N;
    type ans=0;
    while(dep[start]<N)
    {
        if(u==end)
        {
            type Min=INF;
            for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
                if(Min>edge[i].cap-edge[i].flow)
                   Min=edge[i].cap-edge[i].flow;
            for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
            {
                edge[i].flow+=Min;
                edge[i^1].flow-=Min;
            }
            u = start;
            ans+=Min;
            continue;
        }
        bool flag=false;
        int v;
        for(int i=cur[u];i !=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(edge[i].cap-edge[i].flow&&dep[v]+1==dep[u])
            {
                flag=true;
                cur[u]=pre[v]=i;
                break;
            }
        }
        if(flag)
        {
            u=v;
            continue;
        }
        int Min=N;
        for(int i=head[u];i!=-1;i=edge[i].next)
            if(edge[i].cap-edge[i].flow&&dep[edge[i].to]<Min)
        {
            Min=dep[edge[i].to];
            cur[u]=i;
        }
        gap[dep[u]]--;
        if(!gap[dep[u]]) return ans;
        dep[u]=Min+1;
        gap[dep[u]]++;
        if(u!=start) u=edge[pre[u]^1].to;
    }
    return ans;
}

long long a[15], b[15], w[105][105];

void solve () {
    s = 0;
    int node = n + 10;
    long long tot = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = i+1; j <= n; j++) {
            node++;
            add_edge (s, node, w[i][j]+w[j][i], 0);
            add_edge (node, i, INF, 0);
            add_edge (node, j, INF, 0);
            tot += w[i][j]+w[j][i];
        }
    }
    t = ++node;
    for (int i = 1; i <= n; i++) {
        add_edge (i, str[i]+n+1, INF, 0);
        add_edge (i, t, a[str[i]+1], 0);
    }
    for (int i = 1; i <= 10; i++) {
        add_edge (i+n, t, b[i]-a[i], 0);
    }
    long long ans = sap (s, t, node);
    printf ("%lld\n", tot-ans);
}

int main () {
    int t, kase = 0;
    scanf ("%d", &t);
    while (t--) {
        scanf ("%d", &n);
        if (n == 0) {
            for (int i = 1; i <= 10; i++) {
                int u , v;
                scanf ("%d%d", &u, &v);
            }
            printf ("Case #%d: 0\n", ++kase);
            continue;
        }
        scanf ("%s", str+1);
        init ();
        for (int i = 1; i <= n; i++) str[i] -= '0';
        for (int i = 1; i <= 10; i++) {
            scanf ("%lld%lld", &a[i], &b[i]);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                scanf ("%lld", &w[i][j]);
            }
        }
        printf ("Case #%d: ", ++kase);
        solve ();
    }
    return 0;
}
/*
10
10
9874563210
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值