测试地址:星际竞速
做法:本题需要用到费用流+拆点。
分析题目中要求的路径,我们发现在一条满足要求的路径中,除起点那颗不和任何点连通的点外,所有点的入度都是
1
1
,那么这些入度从哪里来呢?一是从某个星球飞过来,二是从某个星球跳过来(这话说的……)。因为一开始我们也是从一个星球跳到某个星球上的,所以我们把所有跳的决策都看做从起点跳过去,那么每个点的入度来源就只有两个,一是从某个星球飞过来,二是从起点跳过来。因为路径上每个点的出度也只有,那么我们显然可以把一个点拆成两个点,一个表示出度点,一个表示入度点。那么我们可以这样建图:
从源点向每个出度点连容量为
1
1
,费用为的边,再从源点向每个入度点连容量为
1
1
,费用为跳跃到该点的定位时间,然后从每个入度点向汇点连容量为,费用为
0
0
的边,最后对于原来一条可以走的边,从
x
x
的出度点向的入度点连容量为
1
1
<script type="math/tex" id="MathJax-Element-1055">1</script>,费用为所需时间的边。这样每一份流量都代表一次决策,即从某个星球飞向另一个星球,或直接跳跃到某个星球,那么在最大流的情况下能得到的最小费用就是我们要求的答案了,所以直接做即可。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,S,T,first[2010]={0},tot=1;
int dis[2010],laste[2010],last[2010];
queue<int> Q;
bool vis[2010];
struct edge
{
int v,next,f,c;
}e[50010];
void insert(int a,int b,int f,int c)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
S=(n<<1)+1,T=(n<<1)+2;
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
insert(S,i,1,0);
insert(S,n+i,1,t);
insert(n+i,T,1,0);
}
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if (u>v) swap(u,v);
insert(u,n+v,1,w);
}
}
bool spfa()
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=T;i++)
dis[i]=inf;
dis[S]=0;
vis[S]=1;
Q.push(S);
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&dis[e[i].v]>dis[v]+e[i].c)
{
laste[e[i].v]=i;
last[e[i].v]=v;
dis[e[i].v]=dis[v]+e[i].c;
if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
}
vis[v]=0;
}
return dis[T]!=inf;
}
void mincost()
{
int minc=0;
while(spfa())
{
int x=T,maxf=inf;
while(x!=S)
{
maxf=min(maxf,e[laste[x]].f);
x=last[x];
}
x=T;
while(x!=S)
{
e[laste[x]].f-=maxf;
e[laste[x]^1].f+=maxf;
x=last[x];
}
minc+=maxf*dis[T];
}
printf("%d",minc);
}
int main()
{
init();
mincost();
return 0;
}