您的位置:首页 > 其它

Kejin Game UVALive - 7264 拆点+最小割

2017-03-28 17:15 423 查看
传送门:UVALive - 7264

题意:给定一颗类似于dnf、lol点天赋时候的天赋(技能)树,技能获得的前提是他的前置技能都获得了,作为一个RMB玩家,你有特权:

1.直接花费一定数量的钱获得某个技能。

2.花费一定数量的钱将一个技能的某一个前置关系取消,即将前置技能到该技能的边消除(但不会获得该前置技能)。

如果正常学习技能的话每一个技能都要花费一定量的时间,问获得指定的技能的最少的花费是多少。

思路:读完提就会知道是图论相关,第一反应是最短路什么的,但是这些复杂的优先级关系并非最短路能处理的。后来看题解发现是拆点+最小割,其实就是最大流模板题,随便套个网络流算法的模板就能过,但是此题重点和难点在于如何建图。

假设要获得的技能为S。

1.将所有点拆成i和i'两个点,在建好的图中就用i和i+n分别表示。

2.若i->j有边(i为j的前置技能),则将i'到j建边,权值为用钱将该边消除的花费。

3.将源点和i建边,边权为正常学习该技能花费的时间(满足前置技能以后再学的花费)。

4.将i与i'建边,权值为用钱直接获得该点的花费(氪金,不用管前置技能)。

5.将S’与汇点建边,权值为inf。

这样最大流跑完了以后,S’与汇点之间正向边的流量就是源点与汇点的最小割,也是要求的最小花费。自己画个简单的图模拟一下很容易就明白为什么这样是对的,但是这种将题中不同条件转化为对应的边的思想是很难一天两天练成的,建图也是图论相关题的奥义所在。多刷题,少看题解,争取早日领略其中奥义,共勉。

代码:

//ISAP int
#include<bits/stdc++.h>
#define ll long long
#define MAXN 1010
#define inf 0x3f3f3f3f
using namespace std;
int n,m;//题目输入点数,边数
struct Edge{
int v,next;
int cap,flow;
}edge[MAXN*100];
int cur[MAXN],pre[MAXN],gap[MAXN],path[MAXN],dep[MAXN];
int cnt=0;//实际存储总边数
void init()
{
cnt=0;
memset(pre,-1,sizeof(pre));
}
void add(int u,int v,int w)//加边
{
edge[cnt].v=v;
edge[cnt].cap=w;
edge[cnt].flow=0;
edge[cnt].next=pre[u];
pre[u]=cnt++;
}
bool bfs(int s,int t)//其实这个bfs可以融合到下面的迭代里,但是好像是时间要长
{
memset(dep,-1,sizeof(dep));
memset(gap,0,sizeof(gap));
gap[0]=1;
dep[t]=0;
queue<int>q;
while(!q.empty())
q.pop();
q.push(t);//从汇点开始反向建层次图
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=pre[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(dep[v]==-1&&edge[i^1].cap>edge[i^1].flow)//注意是从汇点反向bfs,但应该判断正向弧的余量
{
dep[v]=dep[u]+1;
gap[dep[v]]++;
q.push(v);
//if(v==s)//感觉这两句优化加了一般没错,但是有的题可能会错,所以还是注释出来,到时候视情况而定
//break;
}
}
}
return dep[s]!=-1;
}
int isap(int s,int t)
{
bfs(s,t);
memcpy(cur,pre,sizeof(pre));
int u=s;
path[u]=-1;
int ans=0;
while(dep[s]<n)//迭代寻找增广路
{
if(u==t)
{
int f=inf;
for(int i=path[u];i!=-1;i=path[edge[i^1].v])//修改找到的增广路
f=min(f,edge[i].cap-edge[i].flow);
for(int i=path[u];i!=-1;i=path[edge[i^1].v])
{
edge[i].flow+=f;
edge[i^1].flow-=f;
}
ans+=f;
u=s;
continue;
}
bool flag=false;
int v;
for(int i=cur[u];i!=-1;i=edge[i].next)
{
v=edge[i].v;
if(dep[v]+1==dep[u]&&edge[i].cap-edge[i].flow)
{
cur[u]=path[v]=i;//当前弧优化
flag=true;
break;
}
}
if(flag)
{
u=v;
continue;
}
int x=n;
if(!(--gap[dep[u]]))return ans;//gap优化
for(int i=pre[u];i!=-1;i=edge[i].next)
{
if(edge[i].cap-edge[i].flow&&dep[edge[i].v]<x)
{
x=dep[edge[i].v];
cur[u]=i;//常数优化
}
}
dep[u]=x+1;
gap[dep[u]]++;
if(u!=s)//当前点没有增广路则后退一个点
u=edge[path[u]^1].v;
}
return ans;
}
int main()
{
//std::ios::sync_with_stdio(0);
int T,s;
cin>>T;
while(T--)
{
init();
scanf("%d%d%d",&n,&m,&s);
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a+n,b,c);
add(b,a+n,0);//注意加反向边时一般初始流量为0!
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
add(0,i,a);
add(i,0,0);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
add(i,i+n,a);
add(i+n,i,0);
}
add(s+n,2*n+1,inf);
add(2*n+1,s+n,0);
n=2*n+1;
printf("%d\n",isap(0,n));
}
return 0;
}


isap是套的原来写好的板子。上面的子函数看似挺长好深奥的样子,实际上就是个模板,主函数中建边的过程才是网络流一类题目的难点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: