[ABC326G] Unlock Achievement

思路

题目&思考

翻译一下题目:已知有 n 个技能,每个技能可以耗费 $c _ {i}$ 升一级,每个技能最多可以升到 ( $ 1\ \leq\ L_{i,j}\ \leq\ 5 $ ) ,升级的同事可以获得成就,共有 m 个成就,每个成就可以获得 $c _ {i}$ 的收益,每个成就需要每个技能达到一定的限制,询问获得的奖励与所需成本之差。

看到有这么多点以及给出的条件十分繁杂的前提下,可以考虑网络流,对于网络流,最难的是网络流的建模。

网络流的建模:

题目所给的条件没有明显源点汇点,所以考虑设立一个超级源点 $S$ 以及超级汇点 $T$。

由于每个技能可以升级,单个点不能处理每一级之间的关系。

考虑拆点

将每一个技能拆成五个点,代表此技能的五级,除第五级外每一级向自己的下一级连一条流量为 inf 的边(保证它不会被割掉)。

对于源点,其连的是花费,因为所有的技能一开始都是一级,所以在一级的时候不需要花费任何代价,超级源点 S 向除了第一级的所有点连一条流量为 $c _ {i}$ 的边。

这样连边就可以保证每一级的最大流量是技能升级需要的花费。

对于汇点,由于成就的条件十分繁杂,对于每个成就,考虑新建节点 $p_ {i}$,其限制条件可以转化为P1361 小M的作物,将每个成就需要的每个技能级数的点连向当前点 $p_ {i}$,这样就保证了成就限制条件,最后每个 $p_ {i}$ 向超级汇点连一条流量为 $c _ {i}$ 的边,代表此成就的奖励。

所以综上所述,本题的建模规则:

1:将每个点拆成五个点,每一级向下一级连一条流量 inf 的边。

2:源点向每个技能每一级(除了第一级)连一条流量为 $c _ {i}$ 的边。

3:对于每一个成就新建一个点,将其限制的技能级数连向这个点。

4:每一个代表成就的点向汇点连一条流量为 $c _ {i}$ 的边。

这道题利用的便是网络流中的最小割,因为我们要求的是升级每个技能至某一级,最大化获得的奖励与所需成本之差,最小割求得的便是升级所用的花费+没有达成的成就之和,成就的总和 - dinic 求出的最小割=获得的奖励与所需成本之差。

在网络流中,最小割的数值上=最大流,所以可以跑一遍 dinic 板子,最后要求的答案就是所有的 $c _ {i}$ 之和减去求得的最小割的值。

更多细节参见代码。

代码

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdio>
#define inf 1e9
using namespace std;

inline int min(int x,int y){return x>y?y:x;}

int n,m,s,t,maxx=0;

int cnt=-1,res=0;;
struct N{
    int to,next,val; 
}; N p[800005];
int head[100005],cur[100005];
int c[55],a[55],d[10005];
int mapp[55][55];

inline void add(int a,int b,int c)
{
    ++cnt;
    p[cnt].next=head[a];
    head[a]=cnt;
    p[cnt].to=b;
    p[cnt].val=c;
    return ;
}

inline int bfs()
{
    memset(d,-1,sizeof(d));
    queue<int> q;
    q.push(s); 
    d[s]=0;
    while(q.size()>0)
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=p[i].next)
        {
            int v=p[i].to;
            if(d[v]==-1&&p[i].val>0)
            {
                d[v]=d[u]+1;
                q.push(v);
            }
        }
    }
    if(d[t]==-1) return 0;
    else return 1;
}

inline int dfs(int u,int limit)
{
    if(u==t||!limit) return limit;
    int flow=0,sum=0;
    for(int i=cur[u];i!=-1;i=p[i].next)
    {
        int v=p[i].to;
        cur[u]=i;
        if(d[v]==d[u]+1&&p[i].val>0)
        {
            sum=dfs(v,min(limit,p[i].val));
            limit-=sum;
            flow+=sum;
            p[i].val-=sum;
            p[i^1].val+=sum;
            if(!limit) break;
        }
    }
    if(!flow) d[u]=-1;
    return flow;
}

inline void dinic()
{
    int ans=0;
    while(bfs())
    {
        for(int i=0;i<=t;i++) cur[i]=head[i];//弧优化 
        ans+=dfs(s,inf); 
    }
    cout<<maxx-ans<<endl;
    return ;
}//板子

int main()
{
    memset(head,-1,sizeof(head));
    cin>>n>>m;
    s=0,t=5*n+m+1;//超级源点和超级汇点
    for(int i=1;i<=n;i++)
    {
        cin>>c[i];
        ++res;
        for(int j=2;j<=5;j++)
        {
            res++;
            add(res-1,res,inf),add(res,res-1,0);
            add(s,res,c[i]),add(res,s,0);
        }
    }//拆点,建图规则1&2,点编号 1-5*n
    for(int i=1;i<=m;i++)
    {
        cin>>a[i];
        add(5*n+i,t,a[i]),add(t,5*n+i,0);
        maxx+=a[i];
    }//建图规则4,点编号5*n+1-5*n+m
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>mapp[i][j];
            if(mapp[i][j]>1) add(5*(j-1)+mapp[i][j],5*n+i,inf),add(5*n+i,5*(j-1)+mapp[i][j],0);
        }//第i个点点第j级的编号为5(i-1)+j; 
    }//建图规则3
    dinic();//板子

    return 0;
}

在题目点数众多,限制条件十分繁杂的情况下,都可以考虑使用最小割的方法。和这题处理方法类似的有:

P1646 [国家集训队] happiness

P4313 文理分科

P1361 小M的作物

P2057 [SHOI2007] 善意的投票

P1935 [国家集训队] 圈地计划

posted @ 2023-10-31 08:06  keep_of_silence  阅读(4)  评论(0编辑  收藏  举报  来源
/*
*/