UPC-6623 MUL(最大权闭合子图)

题目描述
We have N gemstones labeled 1 through N.
You can perform the following operation any number of times (possibly zero).
Select a positive integer x, and smash all the gems labeled with multiples of x.
Then, for each i, if the gem labeled i remains without getting smashed, you will receive ai yen (the currency of Japan). However, ai may be negative, in which case you will be charged money.
By optimally performing the operation, how much yen can you earn?

Constraints
All input values are integers.
1≤N≤100
|ai|≤109

输入
Input is given from Standard Input in the following format:
N
a1 a2 … aN

输出
Print the maximum amount of money that can be earned.

样例输入
6
1 2 -6 4 5 3

样例输出
12

提示
It is optimal to smash Gem 3 and 6.

题意:有n个数,给出n个数的权值,现在要选择一个或多个x,删除x以及x的倍数,使得剩余数字权值之和最大。
如样例中选择x=3,删除-6和3,那么结果即1 +2 +4 +5 =12

最大权闭合子图模板题,建图比较奇葩,正权点全都连到汇点,源点连向所有负权点。因为这道题要求的是选择的是不计算的,而不是选择的是被计算的。根据数字间的关系,一个点连一条容量为无限大的边指向其所有倍数。表示要删除这个点,即把其倍数全部删除。

考虑两种可能:
当容量“左小右大”时,即负权容量限制了正权容量,跑出来的将是被减去的负权,表示该点未删,所以在总权值的计算中算上了这个负权。因为该负权所连向的正权较大,删去了该点连带的是一些大的正权点也被删掉,但不删该点,较大的正权会填补较小的负权,因此这样才是最优的选择。即不删除该负权,使得该负权倍数的点的正权加到加和中。

当容量“左大右小”时,即负权容量很大,正权容量很小,那么跑出来的流量即被正权所限制,那么这些正权跑出来的流量是被减去的,表示在删除过程中,因为删除了较大的负权点,连带其倍数,正权节点也被删掉了,所以在求得权值和之后,要减去这些被删去而没有实际加上的值,被删去的原因是因为其因子出现了较大负权,必须删掉保证正权的值,否则将损失更加严重,因此该点被较大负权影响而不得不删去。

#include<bits/stdc++.h>
#define LL long long
#define M(a,b) memset(a,b,sizeof a)
#define pb(x) push_back(x)
using namespace std;
const int maxn=108;
const int inf=0x3f3f3f3f;
struct edge
{
    int to,rev;
    LL val;
    edge() {}
    edge(int a,LL b,int c)
    {
        to=a;
        val=b;
        rev=c;
    }
};
int n;
vector<edge>mp[maxn];
int dep[maxn];
inline void add(int from,int to,LL val)
{
    mp[from].pb(edge(to,val,mp[to].size()));
    mp[to].pb(edge(from,0,mp[from].size()-1));
}
bool bfs()
{
    M(dep,-1);
    queue<int>q;
    while(!q.empty())q.pop();
    dep[0]=0;
    q.push(0);
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        if(tmp==n+1)return true;
        for(int i=0;i<mp[tmp].size();i++)
        {
            int &to=mp[tmp][i].to;
            LL flow=mp[tmp][i].val;
            if(dep[to]==-1&&flow)
            {
                dep[to]=dep[tmp]+1;
                q.push(to);

            }
        }
    }
    return false;
}
LL dfs(int s,int t,LL flow)
{
    if(s==t)return flow;
    LL pre=0;
    for(int i=0;i<mp[s].size();i++)
    {
        int &to=mp[s][i].to;
        LL val=mp[s][i].val;
        if(dep[s]+1==dep[to]&&val)
        {
            int tmp=min(flow-pre,val);
            int sub=dfs(to,t,tmp);
            mp[s][i].val-=sub;
            mp[to][mp[s][i].rev].val+=sub;
            pre+=sub;
            if(pre==flow)return pre;
        }
    }
    return pre;
}
LL dinic()
{
    LL ans=0;
    while(bfs())ans+=dfs(0,n+1,inf);
    return ans;
}
int main()
{
    LL val,ans=0;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)mp[i].clear();
    for(int i=1; i<=n; i++)
    {
        scanf("%lld",&val);
        if(val>0)add(i,n+1,val),ans+=val;///正权连汇点
        else add(0,i,-val);///负权连源点
        for(int j=i+i;j<=n;j+=i) add(i,j,inf);///当给一个点连边时,应该向所有该点的倍数连一条容量无限的边,因为是选择这个x其倍数一定被选,因此是该点指向倍数的有向边
    }
    printf("%lld
",ans-dinic());
}
原文地址:https://www.cnblogs.com/kuronekonano/p/11135717.html