1. 程式人生 > 實用技巧 >【解題報告】P4766 [CERC2014]Outer space invaders

【解題報告】P4766 [CERC2014]Outer space invaders

這道題作為區間DP的補充還是非常有意思的。

一句話題意:

就是你知道每一個外星人的出生時間和消失時間和他和你的距離,為了消滅完他們!我們需要用一個很NB的武器,就是一個什麼可以攻擊一個圓的武器(以自己為圓心),每次消耗的能量為攻擊半徑,問我們消滅所有外星人消耗的最小的能量。

這道題首先我們應該想到的是首先先對所有的時間進行離散化,因為我們發現,只有最多300個外星人,但是時間的大小是10000,有一點大,但是隻要我們離散化以後,開一個二維陣列還是非常的合理的。

然後我們呢類似於合併石子的思路,我們定義當前二位動態規劃陣列的一維存的是從一個時間,二維存的是到另一個時間點,表示的是消滅此時間區間的外星人所需要的最小的能量。

然後我們就可以非常快速的推出動態轉移方程式:

dp[s][e]=min(dp[s][e],dp[s][k-1]+a[id].v+dp[k+1][e]);

但是我們要明白的一點就是,我們這裡肯定是要打到外星人,那麼我們就必須確保這一段區間中存在外星人,並且一定是該區間距離中心最遠的外星人。所以所我們每次枚舉出來一個區間後一定要看該區間中有沒有外星人存在並且要保證當前區間完全包含外星人,否則會出現混亂。然後我們就列舉這個k的時間點(k必須在我們枚舉出的外星人存在範圍內)然後進行dp取最小即可。

注意我們一定是從小區間開始然後慢慢合成到大區間!!!

所以說大家可以認真的思考一下程式碼中動態規劃前面兩個for迴圈為什麼要這樣寫!

參考程式碼:

#include<bits/stdc++.h>
#define BIG 3000005
using namespace std;
struct sd{
    int l,r,v;
}a[305];
int t,n;
int que[305*5],count_que=0,dp[2*305][2*305];
void manage()
{
    count_que=0;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].v);
        que[++count_que]=a[i].l;
        que[++count_que]=a[i].r;
    }
    sort(que+1,que+count_que+1);
    count_que=unique(que+1,que+count_que+1)-que-1;
    for(int i=1;i<=n;++i) a[i].l=lower_bound(que+1,que+1+count_que,a[i].l)-que,a[i].r=lower_bound(que+1,que+1+count_que,a[i].r)-que;//上面都是讀入和離散化
    //下面是真正的區間DP
    for(int w=0;w<count_que;++w)
    {
        for(int s=1;s+w<=count_que;++s)
        {
            int e=s+w,id=-1;
            for(int i=1;i<=n;++i)if(s<=a[i].l&&a[i].r<=e&&(id==-1||a[i].v>a[id].v))id=i;
            if(id==-1){ dp[s][e] = 0;continue;}
            dp[s][e]=BIG;
            for(int k=a[id].l;k<=a[id].r;++k) dp[s][e]=min(dp[s][e],dp[s][k-1]+a[id].v+dp[k+1][e]);
        }
    }
    printf("%d\n", dp[1][count_que]);
}
int main()
{
    scanf("%d",&t);
    for(int i=1;i<=t;++i) manage();
    return 0;
}

By njc