您的位置:首页 > 其它

poj 1036 Gangster -- 区间型DP解法

2012-05-31 23:57 302 查看
/*
*  poj 1036 Gangster

题目大意:
N个抢匪陆续的进入一家餐馆,餐馆的们有K个开放度,每个绑匪只有在开放度适合
自己时才能进入,并给餐馆带来相应收益P。问通过控制门的开、关(一个时间单位
门只能开1、关1或者保持不变),餐馆能获得的最大收益是多少?

数学模型:
1、假设所有歹徒已经按照进入时刻,按照从先到后的顺序进行了排序。-- 排序是必要步骤,后续讲到

2、s(i,j)表示介于第i个活动和第j个歹徒之间,所有同时与与歹徒g[i]和g[j]兼容的
歹徒组成的集合。

f(i,j)表示通过控制门的开关使得集合s(i,j)中歹徒进入餐馆所能获得的最大收益。

3、兼容性的数学表示: -- 歹徒a和b兼容(不失一般性,假设T[a]>T[b])
T[a]-T[b] >= |S[a]-S[b]|

4、为了表示题目的原始要求,引入辅助歹徒g[0]和g[n+1]。
4.1、g[0]的进餐馆时刻T[0]=0,需要门的开放度O[0]=0
4.2、g[n+1]的进餐馆时刻T[n+1]=6000,需要门的开放度O[n+1]=0 -- g[n+1]的取值后续会解释
则所有的歹徒均与这两个辅助歹徒兼容,最后求出f(0, n+1)即是所求。

5、由以上4点,得出由如下递归式成立:

|-- 0               if s(i,j)={NULL} -- 集合s(i,j)为空
f(i, j) = ---|
|-- max{f(i,k)+P[k]+f(k,j) | g[k] belong_to s(i,j)}

针对每个选择g[k]会将原问题划分成两个子问题f(i,k)和f(k,j),但是子问题的解
能否构成原问题的解尚需考证。如果不能,则该递归式毫无意义。如果能,需要进
一步考虑,这些子问题是否相互重复 -- 使用动态规划的第二个特征。

第1步的排序,保证了两个子问题的解可以合并成原问题的解。如果不排序,则有如
下反例:

G |  T   K
--|--------
x |  8   1
|
k |  1   1
|
y |  10  10

上例中歹徒g[x] belong_to s(i,k)、g[y] belong_to s(k,j),但是明显g[x]与g[y]
互不兼容,因此他们构成的解也肯定不是原问题的正确解!

6、证明: 按照进入时刻排序后,两个子问题f(i,k)和f(k,j)的解可以构成原问题的解。

证明:
假设存在歹徒g[x] belong_to s(i,k)和g[y] belong_to s(k,j),则根据兼容
性,有如下表达式成立:

I) T[k]-T[x] >= |S[k]-S[x]|
II) T[y]-T[k] >= |S[y]-S[k]|

合并表达式I、II得:
III) T[y]-T[x] >= |S[y]-S[k]| + |S[k]-S[x]|  -->绝对值的和 >= 和的绝对值
>= |S[y]-S[x]|

因此可以得出结论: 集合s(i,k)与集合s(k,j)完全兼容,因此{s(i,k) U g[k] U s(k,j)}
是原问题的一个解。

问题:
1、为什么要排序? 不排序该递归式就不成立了吗?
2、T[n+1]需要设置成6000吗? 小点行不行?

解答:
1、排序保证了两个子问题f(i,k)与f(k,j)的解可以构成原问题的一个解,是递归式成立的基础。
不排序,递归式不成立,上面已经给出了反例。

2、因为原题目已经明确说明了K最大取100,因此T[n+1]设置成大于等于3100的值均可。
目的是保证,歹徒g[n+1]一定与所有歹徒兼容,即满足兼容的数学表达式。
*/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>

namespace {
using namespace std;

typedef struct GANGSTER
{
int t, k, p;
}GANGSTER_S;

int gangster_comp(const void *pOP1, const void *pOP2)
{
GANGSTER_S *pG1 = (GANGSTER_S *)pOP1;
GANGSTER_S *pG2 = (GANGSTER_S *)pOP2;

if (pG1->t < pG2->t)  return -1;
if (pG1->t == pG2->t) return 0;
if (pG1->t > pG2->t)  return 1;
}

inline bool gangster_compatible(const GANGSTER_S &G1, const GANGSTER_S &G2)
{
return abs(G1.t-G2.t) >= abs(G1.k-G2.k);
}

const int N_MAX = 100;
GANGSTER_S G[N_MAX+1]; // 歹徒存储

int f[N_MAX+2][N_MAX+2]; // f(i, j)
int n; // 实际有效的歹徒个数

int gangster()
{
qsort(&G[1], n, sizeof(G[1]), gangster_comp); // 排序,保证子问题的解可以构成原问题的解

G[n+1].t = 6000; // 辅助歹徒,g[n+1]

for (int l=2; l<=n+1; l++) // 每次处理的长度
{
for (int i=0; i<=n; i++)
{
int j=i+l; // 当前行处理的列序数
if (j<=n+1) // 防止越界
{
for (int k=i+1; k<j; k++) // 枚举k值,查找兼容的歹徒
{
// 兼容性判断
if (gangster_compatible(G[i], G[k]) && gangster_compatible(G[k], G[j]))
{
if (f[i][k]+G[k].p+f[k][j] > f[i][j])
f[i][j] = f[i][k]+G[k].p+f[k][j];
}
}
}
}
}

return f[0][n+1];
}
}

int main()
{
int N, K, T;
scanf("%d%d%d", &N, &K, &T);

for (int i=1; i<=N; i++)
{
scanf("%d", &G[i].t);
}
for (int i=1; i<=N; i++)
{
scanf("%d", &G[i].p);
}
for (int i=1; i<=N; i++)
{
scanf("%d", &G[i].k);
}

int i=1; n=N;
while (i<=n)
{
// 去除无效的歹徒
if (G[i].k>K || G[i].k>T ||G[i].k>G[i].t)
{
swap(G[i], G
);
n--;

continue;
}

++i;
}

printf("%d\n", gangster());

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: