HDU 3851解题报告

Beat It!

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 125536/65536 K (Java/Others)
Total Submission(s): 853    Accepted Submission(s): 263


Problem Description
There's a monster-attacking game on the Renren's open platform, and the game's background are showing below.
Where lies a strange country, lices a strange monster. The strange people in that strange country are persecuted by the strange monster. So teh strange people make a strange cannon to defeat the strange monster.
As far as we know, the strange cannon is powerful, but once it works, it should not work in next T hours (Eg. If it fired at time 1, then time 2 to time T it cannot work). When the strange monster get hit during the daylight, it get PD point hurt; it will get PN point hurt when it happens during the night. As the country is strange, the time of the daylight and night changes eveyday. In the ith day, the daylight starts at time 1 and ends at time T1[i], then from time (T1[i]+1) to time (T1[i]+T2[i]) is night.
Here comes a question: what is the maximal hurt point would the strange monster got after N days?
 

Input
First line contains an integer Q (1<=Q<=20), indicates the number of test cases.
For each case, first line contains 4 integers: N (1<=N<=1000), T (1<=T<=100), PD, PN.
Then followed N lines, the (i+1)th line contains 2 integer T1[i], T2[i].
(PD, PN, T1[i], T2[i] are all non-negative and fit the 32bit integer)
 

Output
For each case print one line, output the answer. See the sample for format.

 

Sample Input
  
  
1 2 5 20 5 6 10 2 1
 

Sample Output
  
  
Case 1: 65
 

Source
 

Recommend
chenyongfu   |   We have carefully selected several similar problems for you:   3848  3849  3854  3858  3857 

       在做这道题的时候,第一反应对于题目描述中说的打完一枪以后,之后T时间内不能再打。感觉必定是要对时间进行dp。但是看到下面的input数据时看到t1[i]和t2[i]都是32位正数。这时感觉没法做了,因为如果对时间T来开数组的话肯定内存不够。。而且也会超时。。看到了炮弹的冷却时间T为100左右,凭借高中做数学题的经验,数据范围对解题有时也会有所暗示,但是没想明白T范围突然这么小,而不是32位正数大小。不知道有啥玄机,没想通。。。脑子里虽然也闪现过是不是要状态压缩呢,但之后由于意志不坚定,加上不知道如何状态压缩,便放弃了这个念头。。然后尝试别的状态转移方程,假设dp[i]表示到第i天的时候获得的最大伤害。这样考虑dp[i-1]之间的关系,dp[i-1]加上当前第i天获得的最大伤害。但是这样一想还是有些不太对,因为t与t1[i],t2[i]之间的关系很不确定。dp[i-1]加上当前第i天的最大伤害也没道理,因为t与t1[i],t2[i]这些值的不确定性,可能之前某一天多打一枪,而打完这一枪,后边的一天或者几天都打不了。。这样dp之所以错误,还有一个问题,就是前边的状态(错误方法下的错误状态)会对后边的某些状态进行影响,这也不符合动态规划中无后续性的特点。而且也不符合最优子结构的性质。感觉这样做只是取局部的某一天的最优解,不断从i=1向后推。但是这样局部最优解并不能的出最终全局的最优解。这样做法既不是动态规划,也不是贪心。因为没有贪心选择性,所以也不是贪心。故而是错误的。。当然,如果把状态再细化到dp[i][j][k],j=0表示第i天的白天,j=1表示第i天的晚上,k=0表示该时间段不会覆盖下一小段(白天或晚上),k=1表示该时间段会覆盖下一小段。但是这样定义状态的问题在于覆盖这里,还是老话,因为t和t1[i]和t2[i]的大小关系不确定,无法确定该小段到底会覆盖多少段。所以只是写覆盖一段是不对的。因而无论怎样,都是时间来决定状态。这一点应该坚定才对。这道题做了好长时间,想了很多不靠谱的状态转移方程,都一个个被推翻。感觉学了好长时间的dp,但是掌握还是不太牢靠,对于算法本身的特点还是理解不深刻。动态规划中强调的状态,状态转移,最优决策,最优子结构,无后效性这些的真正含义还是没有理解清楚,没有想明白。多从算法实质上思考才能真正理解。这一点现在觉得越来越重要。

       上面都是解这道题的过程中遇到的各种问题。下面还是从时间上进行dp。这个dp的方程非常简单,就是dp[i]=max(dp[i-1],dp[i-t]+p[i])(p[i]是i时刻的时候受到的伤害)。注意在本题的处理过程中,由于T的值比较小,所以在进行最优决策的时候,当t>2*T时,这时多出来的部分一定要打枪。而预留的2*T是用来承接前一个时间段和后一个时间段的。因而除去最优决策的部分,对于剩下的时间段连在一起,然后进行dp。可是将剩下的段连起来,这样真的对吗?相当于每段挖去了中间的一段,这样能否保证dp的正确性?仔细分析一下,将每一个时间短段中的2*T部分取出来,如果选取前T时间段中的某点射击,那么这个点后边的T时间内不能射击。可是这个点后边的T时间内要是包含我们挖去的中间段怎么办?当然,我们可以平移区间,虽然前T时间中某点射击占用了中间段的时间,但是通过将中间段向后平移,最终后边预留的那个T段时间内就会少一段时间。这样与将前后两段为T的时间拼接起来是等效的。这一点一定要想清楚。如果不等效的话就不能这么做。

       然后,解决了一系列问题后,求解就简单多了。这里还要和之前做过的一道dp题进行类比。这两道题的思想有很大的相似点。就是POJ 3744

       http://blog.csdn.net/u012294939/article/details/43926443

       这道题是一道简单的概率dp,但是由于长度太长,而采取了矩阵快速幂对于dp进行了优化。也避免了内存和时间不够的情况。本题也是一样。本题是通过最优决策减少dp的长度。POJ 3744通过矩阵快速幂来加速dp。这两道题在思想方法上有一定的相通之处,不过具体的处理方式有所不同。

       参考代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<ctime>
#include<cstdlib>
#include<iomanip>
#include<utility>
#define pb push_back
#define mp make_pair
#define CLR(x) memset(x,0,sizeof(x))
#define _CLR(x) memset(x,-1,sizeof(x))
#define REP(i,n) for(int i=0;i<n;i++)
#define Debug(x) cout<<#x<<"="<<x<<" "<<endl
#define REP(i,l,r) for(int i=l;i<=r;i++)
#define rep(i,l,r) for(int i=l;i<r;i++)
#define RREP(i,l,r) for(int i=l;i>=r;i--)
#define rrep(i,l,r) for(int i=1;i>r;i--)
#define read(x) scanf("%d",&x)
#define put(x) printf("%d\n",x)
#define ll long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<11
using namespace std;

int q,n,t,pd,pn,s;
ll ans;
ll dp[1000100],res[1000100];

void func(ll T,ll p)
{
    ll k;
    if(T>2*t) k=(T-2*t)/t;
    else k=0;
    ans+=k*p;      //累加固定的最大伤害
    REP(i,1,T-k*t)
        res[++s]=p;
}

int main()
{
   scanf("%d",&q);
   int cas=1;
   while(q--)
   {
       scanf("%d%d%d%d",&n,&t,&pd,&pn);
       ans=0,s=0;
       REP(i,1,n)
       {
           int t1,t2;
           scanf("%d%d",&t1,&t2);
           func(t1,pd);
           func(t2,pn);
       }
       rep(i,1,t)
          dp[i]=res[i];
       REP(i,t,s)
          dp[i]=max(dp[i-1],dp[i-t]+res[i]);
       printf("Case %d: %I64d\n",cas++,ans+dp[s]);
   }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值