題目鏈接:
題意:
有兩種值A,D,A代表攻擊一次怪獸能對怪獸造成的傷害。D代表每回合開始時A的增量。初始值均爲0
給出三種操作,求使用這三種操作在n回合後可以達到的對怪獸傷害的最大值:
1.攻擊怪獸,造成A+a[i]傷害。
2.不攻擊怪獸,但使D增加b[i]。
3.不攻擊怪獸,但使A增加c[i]。
輸入:
樣例數T
每組樣例一個n代表回合
隨後n行每行三個數ai bi ci
輸出:
對於每組樣例,輸出可以對怪獸造成的最大傷害。
思路:
現場賽自閉題,a b c的值都太大了,以至於不知道該怎麼dp。隱隱約約有一種感覺,知道應該按次數dp,也知道應該是倒序,但是dp數組的維度還是沒想明白。
言歸正傳,由於a,b,c都太大,所以我們不能選其值作爲dp的維度,因爲根本開不下。那還有什麼可以dp的東西呢?次數。
題目說了回合最多100次,100次要麼選1,要麼選2,要麼選3,而選2和3本質是相同的,只要知道選2或者3之後哪幾個回合攻擊了,就可以知道選2或者3的收益。但是,“哪幾個回合攻擊”蘊含的信息量太大,它本身是一個集合,對於一個狀態,我們不能存儲一個集合,那麼就該考慮如何把這個信息轉化。
選3的時候,沒必要知道具體是哪些回合攻擊了,只需要知道選3之後攻擊了多少次,就可以得出選3的收益——設選3之後攻擊力j次,則收益爲c[i]*j
選2的時候就比較麻煩了,看似我們必須知道哪些回合攻擊了,才能知道選2的收益有多大。設x爲i回合選2之後選擇攻擊的回合,把收益列出來:
b[i]*((x1-i) + (x2-i) + (x3-i) + ... + (xj - i)) = b[i]*(Σx - i*j)
發現,並不需要記錄攻擊回合的集合,只需要知道選擇攻擊的回合之和即可。
因此,dp數組選取三維數組:dp[i][j][k]:[第i回合][第i回合到第n回合選擇攻擊的次數][攻擊的回合之和] = i到n回合對怪獸造成的傷害
狀態轉移方程:
dp[i][j+1][k+i] = max(dp[i][j+1][k+i],dp[i+1][j][k] + a[i])//選不選操作1
dp[i][j][k] = max(dp[i][j][k],dp[i+1][j][k] + max(j*c[i],(k-i*j)*b[i]))//選不選操作2或3
由於最後一次肯定要選1,所以dp[n][1][n] = a[n],然後從n-1開始,dp方向爲倒序,直到回合1,在回合1裏找最大的ans即可。
代碼:
/*實現爲了節省空間用了滾動數組*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);
void redirect(){
#ifdef LOCAL
freopen("test.txt","r",stdin);
#endif
}
ll a[110],b[110],c[110];
ll dp[2][110][5060];//1-100之和爲5050
int main(){
redirect();
int T,n;
scanf("%d",&T);
while(T--){
ll ans = 0;
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%lld %lld %lld",&a[i],&b[i],&c[i]);
dp[n%2][1][n] = a[n];//最後一次一定選擇攻擊,這裏是貪心。
for(int i = n-1;i > 0;i--){//從第n-1回合到1回合
for(int j = 1;j <= n-i;j++)//j的含義爲——選擇操作前已選攻擊的次數。這裏正序倒序無關緊要,因爲狀態轉移方程只對不同回合數的量轉移。
for(int k = (i+i+j-2)*(j-1)/2 + n;k <= (n-j+1+n)*j/2;k++){//枚舉攻擊回合之和,這裏正序倒序也無關緊要。
//這裏k的最小值和最大值來源於對攻擊回合的選取。
//當從i開始選,連續j-1回合攻擊,第n回合攻擊的情況是回合之和最小的情況。
//從n開始,倒着數j次攻擊是回合數最大的情況。
dp[i%2][j+1][k+i] = max(dp[i%2][j+1][k+i],dp[(i+1)%2][j][k] + a[i]);
dp[i%2][j][k] = max(dp[i%2][j][k],dp[(i+1)%2][j][k] + max(j*c[i],(k-i*j)*b[i]));
}
memset(dp[(i+1)%2],0,sizeof(dp[(i+1)%2]));//滾動數組,故清零
}
//i=1時選dp的最大值爲答案。
for(int j = 1;j <= n;j++)
for(int k = j*(j-1)/2 + n;k <= (n-j+1+n)*j/2;k++)
ans = max(ans,dp[1][j][k]);
printf("%lld\n",ans);
}
return 0;
}
總結:
思路不夠開拓,難點在將集合轉化成一個回合之和那裏。以後碰到dp正序逆序都要想想,有什麼不同,某個狀態的一個集合能不能用一個數替代。