[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 @ 2024-09-20 09:16  call_of_silence  阅读(10)  评论(0编辑  收藏  举报