【SDUTOJ 2414】An interesting game(最小費用最大流)
An interesting game
Time Limit: 2000ms Memory limit: 65536K 有疑問?點這裡^_^
題目描述
Xiao Ming recently designs a little game, in front of player there are N small hillsides put in order, now Xiao Ming wants to increase some hillsides to block the player, so he prepared another M hillsides, but he does not hope it will be too difficult,so only K in M hillsides are selected to add at most. Paying attention to the original N hillsides, between each two can add only one hillside. Xiao Ming expects players from the starting place to reach the destination in turn and passes all the hillsides to make his distance travelled longest. Please help Xiao Ming how to add the hillsides that he prepared. Note: The distance between two hillsides is the absolute value of their height difference.
輸入
The first line of input is T, (1 <= T <= 100) the number of test cases. Each test case starts with three integers N,M,K (2 <= N <= 1000, 1 <= M <= 1000, 1 <= K <= M and 1 <= K < N), which means that the number of original hillsides, the number of hillsides Xiao Ming prepared and The number of most Xiao Ming can choose from he prepared. Then follow two lines, the first line contains N integers Xi (0 <= Xi <= 30), denoting the height of each original hillside, Note: The first integer is player's starting place and the last integer is player's destination. The second line contains M integers Yi (0 <= Yi <= 30), denoting the height of prepared each hillsides.
輸出
For every test case, you should output "Case k: " first in a single line, where k indicates the case number and starts from 1. Then print the distance player can travel longest.
示例輸入
32 1 16 982 1 16 9153 2 15 9 1521 22
示例輸出
Case 1: 3Case 2: 15Case 3: 36
提示
來源
2012年"浪潮杯"山東省第三屆ACM大學生程式設計競賽示例程式
真真沒看出來是個費用流……又丟失了一次比賽中寫流演算法的機會。。。TOT
學以致用學以致用,學了卻不知道該用這個是什麼感覺……
當知道是費用流問題的時候。。。。我的口老血
知道是流量問題了,就比較好辦點了,建圖跑。建圖略複雜,不過也不難寫。
先來發題意:n個山包,每個山包有個高度,現在要從最左邊走到最右邊,兩個山包間的路程是兩山包的高度差(上山/下山) 然後有m個額外的山包,要求從裡面挑k個加到圖中,讓這樣走一遍後路程最長。要求兩個山包間只能加一個新山包,兩端不可新增。
這樣對於初狀態可以跑一遍,得到一個總路程。這樣每當在i和i+1間新增山包j時,其實就是在總路程里加上abs(high[i]-high[j])+abs(high[j]-high[i+1])-abs(high[i]-high[i+1])
也就是去掉之前的路程,加上新加山包後的路程。
這樣再加一個源點與匯點,源點與所有的新山包連線,流量1,費用0。所有的初始山包與匯點連線,流量1,費用0。新山包與初始山包間兩兩連線,流量1,費用就是剛才的式子。這樣你會發現,只需要1~n-1的初始山包。同時如果所有新山包做點,其實可以優化下,因為只有0~30這幾種高度,每個高度做點,跟源點間流量變成這種高度的山包數量即可。
這樣其實還沒完。。跑出來的是不限制k的情況下。再來個流量限制,在剛才的源點前再加個真`源點和它相連,費用0,流量k,這樣就達到了限流的效果。
程式碼如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("data1.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
struct Edge
{
int v,f,w,next;
};
Edge eg[66666];
int head[2333],h[2333],hcnt[2333];
int dis[2333],pre[2333];
bool vis[2333];
int tp,ans,minf;
void Add(int u,int v,int w,int f)
{
eg[tp].v = v;
eg[tp].w = w;
eg[tp].f = f;
eg[tp].next = head[u];
head[u] = tp++;
}
bool bfs(int st,int en)
{
memset(dis,-1,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
queue <int> q;
q.push(st);
dis[st] = 0;
minf = INF;
int u,v,w,f;
while(!q.empty())
{
u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; i != -1; i = eg[i].next)
{
v = eg[i].v;
w = eg[i].w;
f = eg[i].f;
if(f && (dis[v] == -1 || dis[v] < dis[u]+w))
{
dis[v] = dis[u]+w;
minf = min(f,minf);
pre[v] = i;
if(!vis[v])
{
q.push(v);
vis[v] = 1;
}
}
}
}
if(pre[en] == -1) return false;
ans += dis[en];
for(int i = pre[en]; i != -1; i = pre[eg[i^1].v])
{
eg[i].f -= minf;
eg[i^1].f += minf;
}
return true;
}
int main()
{
int t,n,m,k,x;
scanf("%d",&t);
for(int z = 1; z <= t; ++z)
{
scanf("%d%d%d",&n,&m,&k);
memset(head,-1,sizeof(head));
memset(hcnt,0,sizeof(hcnt));
tp = 0;
ans = 0;
for(int i = 0; i < n; ++i)
{
scanf("%d",&h[i]);
if(i) ans += abs(h[i]-h[i-1]);
}
while(m--)
{
scanf("%d",&x);
hcnt[x]++;
}
//n為起點 0~n-2表示初始第i個山爬到i+1山路程 n+1~n+30表示高1~30的山數 n+31表示起點前的一個點(限流) n+32表示終點
Add(n+33,n+31,0,k);
Add(n+31,n+33,0,0);
for(int i = 0; i <= 30; ++i)
{
if(!hcnt[i]) continue;
Add(n+31,n+i,0,hcnt[i]);
Add(n+i,n+31,0,0);
for(int j = 0; j < n-1; ++j)
{
Add(n+i,j,abs(h[j]-i)+abs(h[j+1]-i)-abs(h[j]-h[j+1]),hcnt[i]);
Add(j,n+i,-abs(h[j]-i)-abs(h[j+1]-i)+abs(h[j]-h[j+1]),0);
}
}
for(int j = 0; j < n-1; ++j)
{
Add(j,n+32,0,1);
Add(n+32,j,0,0);
}
while(bfs(n+33,n+32));
printf("Case %d: %d\n",z,ans);
}
return 0;
}