大航海
Time Limit:1000MS Memory Limit:65536K
Description
你的船队进入了一个河道,河道的两边布满繁华的港口。你和你的手下都非常渴望马上进入这些港口进行贸易,以获得利润,但是流域的管理者却告诉你在他们的地盘航行必须遵守他们的法令。河道左岸的 N 个港口被编号为 L1,L2...LN,河道右岸的 M 个港口被编号为 R1,R2...RM。法令规定如果你进入过左岸(右岸)的某个港口 Li(Ri),你就不能再进入编号为 L1,L2...Li(R1,R2...Ri)的港口。法令还规定了 P 条航道,每条航道(i,j)表示你可以从 Li 港驶到 Rj 港,或者从 Rj 港驶到 Li 港。其它港口之间的航行都是不被允许的。一开头你可以选择停靠在任意一个港口,并且任何时候都可以离开这个流域。一旦离开了这个流域,你就不会再回来了。
给定进入每个港口能获取的利润,你能够迅速估计你最多总共能够获得多少利润?
Input
每个输入文件只包含一组数据。
每个文件的第一行包含三个正整数 N,M,P。1≤N,M≤10000,0≤P≤500000。
以下 N 行,每行一个非负整数,第 i 行表示在港口 Li 能获得的利润。以下 M 行,每行一个非负整数,第 i 行表示在港口 Ri 能获得的利润。所有利润的总和不会超过 10^9。
再下来 P 行,每行两个整数 i,j,表示一个航道。
Output
输出一行,表示能够获得的最大的总利润。
Sample Input
3 3 5
5
0
10
10
10
0
1 2
3 1
2 2
2 3
3 2
Sample Output
30
Source
GDKOI2007
刚开始看这道题的时候我是想到用网络流的,不过有一些性质,可以将这题简化。
从这道题的信息,我们可以看出行走路线中,左边的港口和右边的港口编号都是单调递增的。因此,我们可以利用这一个性质去构造一个dp方程。
比较麻烦的是港口分左右两岸,处理上我们不妨分两个dp数组解决。
先单独考虑左岸港口,由于必须保证港口编号递增,将左岸港口从1到n升序枚举;
若左岸i港口与右岸k港口相连,那么显然:
dp_left[i]=max(dp_left[i],dp_right[k]+profit[i][k])
由于i是升序的,方程无后效性。
再考虑右岸港口,在第一重循环枚举左岸港口的情况下,升序枚举与i相连的右岸港口j,则:
dp_right[j]=max(dp_right[j],dp_left[i]+profit[i][j])
统一一下,就写成下面的形式:
for each left //枚举left左港口
for each edge(left,right) //升序枚举right右港口
{
tmp=dp_left[left]; //dp_left[left]更新前先存值用于更新dp_right[right];
update(dp_left[left]); //dp_left[i]=max(dp_left[i],dp_right[k]+profit[i][k])
update(dp_right[right]); //dp_right[j]=max(dp_right[j],tmp+profit[i][j])
}
由于可以在任意港口离开,答案即为max{max{dp_left[i]},max{dp_right[j]}},1<=i<=n,1<=j<=m;
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
int edge[500001][2];
int n,m,p,cnt,ans;
int left[10001];
int right[10001];
int label[10001];
int len[10001];
int dp1[10001],dp2[10001];
void qs(int l,int r)
{
int i=l;
int j=r;
int m=edge[(l+r)/2][0];
int next=edge[(l+r)/2][1];
while (i<=j)
{
while (edge[i][0]<m||(edge[i][0]==m&&edge[i][1]<next)) i++;
while (edge[j][0]>m||(edge[j][0]==m&&edge[j][1]>next)) j--;
if (i<=j)
{
int k=edge[i][0];
edge[i][0]=edge[j][0];
edge[j][0]=k;
k=edge[i][1];
edge[i][1]=edge[j][1];
edge[j][1]=k;
i++;
j--;
}
}
if (i<r) qs(i,r);
if (j>l) qs(l,j);
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for (int i=1;i<=n;i++) scanf("%d",&left[i]);
for (int i=1;i<=m;i++) scanf("%d",&right[i]);
for (int i=1;i<=p;i++)
{
int x,y;
scanf("%d%d",&x,&y);
edge[++cnt][0]=x;
edge[cnt][1]=y;
}
qs(1,cnt);
label[edge[1][0]]=1;
for (int i=2;i<=cnt;i++)
if (edge[i][0]!=edge[i-1][0])
{
len[edge[i-1][0]]=i-label[edge[i-1][0]];
label[edge[i][0]]=i;
}
len[edge[cnt][0]]=cnt-label[edge[cnt][0]]+1;
for (int i=1;i<=n;i++) dp1[i]=left[i];
for (int i=1;i<=m;i++) dp2[i]=right[i];
for (int i=1;i<=n;i++)
{
int t=label[i];
for (int j=t;j<=t+len[i]-1;j++)
{
int k=edge[j][1];
int tmp=dp1[i];
dp1[i]=max(dp1[i],dp2[k]+left[i]);
dp2[k]=max(dp2[k],tmp+right[k]);
}
}
for (int i=1;i<=n;i++) ans=max(ans,dp1[i]);
for (int i=1;i<=m;i++) ans=max(ans,dp2[i]);
printf("%d",ans);
}