CSU 1919:不醉不归(神奇的dp)

来源:互联网 发布:linux 运行rpm文件 编辑:程序博客网 时间:2024/06/09 19:39

1919: 不醉不归 

      Time Limit: 1 Sec     Memory Limit: 128 Mb     Submitted: 81     Solved: 9    


Description

小X和他的朋友们在一起开心地喝酒,差不多喝完了,朋友们凑了K元钱让小X去小卖部买酒。因为是老客户,虽然每瓶酒的质量是不同的,不过小X买酒始终只需要1元一瓶。

小卖部的酒摆在商品货架上,一共有N个摆着酒的货架,每个货架一开始都摆满了M瓶酒,每瓶酒都有一个自身的质量,可能千差万别。对于每个货架,小X每次只能拿货架最外面的那瓶酒。

令小X感到开心的是,现在超市里的这一批酒在搞“开盖有奖”的活动,所以有一些酒买下来之后还可以再拿一瓶酒。

小X心想,反正不用担心那一帮酒鬼朋友们能不能喝完,拿回去的酒总量越多越赚。于是他开心地买酒、开盖兑奖拿酒,直到不能再拿酒了为止。

作为上帝视角的你对于每瓶酒有多少质量,以及哪些酒有奖励都非常清楚,你能求出小X最多能带多少质量的酒回去和他的朋友们一起喝吗?

Input

输入包含不超过10组数据。

对于每组数据,第一行3个正整数,N,M,K(1<=N,M,K<=200),意义如上文所述。接下来有N行,每行M个正整数,第i行第j个数表示第i个货架从外往里数第j瓶酒的质量为x(1<=x<=10000)。接下来一个整数T(0<=T<=N*M),表示小卖部里面总共有T个有奖的酒。接下来是T行,每行两个整数x和y(1<=x<=N,1<=y<=M)描述一个有奖的酒的位置,表示它位于第x个货架从外往里数第y瓶。

每组数据之后有一个空行。

输入数据以三个用空格分开的0结束。

Output

对于每组数据,输出一行一个正整数,表示小X能带回去的酒的最大质量。

Sample Input

4 3 26 5 92 5 54 5 13 5 812 20 0 0

Sample Output

13

Hint

Source

Author

石文斌



        首先,膜石文斌大佬orz……

        此题第一眼望去极像网络流……如果没有再来一瓶的话,直接源点连每个架子的第一瓶酒,然后同一个架子相邻两个酒瓶之间连边,费用为酒瓶质量,流量全部设置为无穷大,最后所有的酒瓶也都连上汇点,流量为1,汇点再连上超级汇点,流量设置为拥有的钱的个数K。这样用流量限制来控制钱,费用来控制质量,跑一边最大费用最大流即可得到所能得到的最大质量。

        然而题目并没有那么好,它有再来一瓶的……于是便想到了最小割,用最大权闭合子图(回忆起那题植物大战僵尸)。虽然说看起来也很像,对于第i个架子上的第j瓶酒,它的先决条件是i,j-1被取到或者之前有再来一瓶。然而好像没办法限定价格,其实这个也确实不太符合模型。

        山穷水尽,只能想dp了。然而这题的dp确实很神奇,老实说我并没有自己想出来,根据题解才大致理解。它是分成两个dp数组,dp0[i][j]表示在前i个架子上花j元钱且最后一瓶酒是在前i个架子中取到的(相当于没有出现再来一瓶)。dp1[i][j]表示在前i个架子上花j元钱且最后一瓶酒不是是在前i个架子中取到的(相当于之前出现了一次再来一瓶,而且这一瓶酒到目前还没有拿)。这样子分成两种情况的dp在比较久远的年代(两年以上)也是见过的,但是实在记不起细节了。至于转移方程的话就得综合理解了。

        和石文斌大佬说的一样,确实不太好讲,于是我也就对着代码说说吧。具体代码如下(抱歉,本人比较水,代码和标程差距不大……):

#include<bits/stdc++.h>using namespace std;int dp0[500][500],dp1[500][500],s[500][500];bool mp[500][500];int main(){int N,M,K;while (~scanf("%d%d%d",&N,&M,&K)&&N&&M&&K){int ans=0;memset(mp,0,sizeof(mp));memset(dp0,-1,sizeof(dp0));memset(dp1,-1,sizeof(dp1));for(int i=1;i<=N;i++)for(int j=1;j<=M;j++){scanf("%d",&s[i][j]);s[i][j]+=s[i][j-1];}int n; scanf("%d",&n);while (n--){int x,y;scanf("%d%d",&x,&y);mp[x][y]=1;}dp0[0][0]=0;for(int i=1;i<=N;i++){dp0[i][0]=dp0[i-1][0];for(int j=1;j<=K&&j<=i*M;j++){dp0[i][j]=dp0[i-1][j];dp1[i][j]=max(dp0[i][j-1],dp1[i-1][j]);for(int k=1,p=1;k<=j&&p<=M;k++)//k表示在第i个架子花k元钱,p表示目前已经取到第p个瓶子for(;p<=M;p++)if (mp[i][p])//当第i个架子的第p个是再来一瓶的瓶子的时候,取完之后有奖码,p可以继续循环而价格不多花钱{if (dp0[i-1][j-k]>=0) dp1[i][j]=max(dp1[i][j],dp0[i-1][j-k]+s[i][p]);//在前i-1个架子中花j-k元,而在第i个架子上花k元刚好取到第p个瓶子,且那个再来一瓶放在之后取if (dp1[i-1][j-k+1]>=0) dp1[i][j]=max(dp1[i][j],dp1[i-1][j-k+1]+s[i][p]);//在前i-1个架子花j-k+1元且之前没有拿的奖码放在第i个架子上用,所以用j-k+1元就可取p个瓶子if (dp0[i-1][j-k+1]>=0) dp0[i][j]=max(dp0[i][j],dp0[i-1][j-k+1]+s[i][p]);//由于当前遇到了一个再来一瓶,所以我们可以把这个奖码用在前i-1行,相当于在前i-1行可以多1元钱} else//当第i个架子第p个不是再来一瓶的时候{if (dp0[i-1][j-k]>=0) dp0[i][j]=max(dp0[i][j],dp0[i-1][j-k]+s[i][p]);//直接从之前的继承if (dp1[i-1][j-k+1]>=0) dp0[i][j]=max(dp0[i][j],dp1[i-1][j-k+1]+s[i][p]);//把之前的奖码用掉p++; break;//没有再来一瓶,所以处理完毕后就要跳出,得多花钱}}if (i==N) ans=max(ans,max(dp0[i][K],dp1[i][K]));}printf("%d\n",ans);}return 0; }


0 0
原创粉丝点击