[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;
}
在题目点数众多,限制条件十分繁杂的情况下,都可以考虑使用最小割的方法。和这题处理方法类似的有: