首先明确一点,如果某一时刻学习了某个技能,之后这个技能的熟练度掉到了0以下,那么这一时刻不如选择别的技能学习,因此最终答案不会受到“熟练度不能减到0以下”的限制。
先考虑朴素的三维dp,设数组abc分别代表三种技能的贡献,设dpi,j,k代表三种技能上次学习的时间分别为i,j,k时的最大贡献,特别的,i(j,k)=0代表没有学习过。我们在遍历时间轴时,可以先枚举当前时间选择的技能,再枚举上一个时间点选择的技能,比如,当前时间点是i,选择了技能1,上一刻也选择了技能1,那么dpi,j,k可以由dpi-1,j,k+ai-(i-j)-(i-k)转移得到,特别的,如果j=0,就不用-(i-j)这一项,k同理,因为如果还没学习,就不会产生损耗。遍历时间轴是O(n)的,枚举两次时间点需要3*3次,然后再枚举j,k的转移,时间复杂度大概为O(n * 9 * max(abc)^2)=O(9e11),显然不可接受。
考虑优化,注意到max(a,b,c)=1e5,并且如果某个技能间隔了x天没有学习,产生的贡献是负的x方级别的,容易想到对于某个技能,两次学习的间隔不会超过某个阈值,比如,如果一个技能200天没有学习,那么不如在100天时学习一下,得到的贡献明显更大。这样一来,时间复杂度就可以接受了,因为对于当前时间点,j和k各自最多往前枚举200多一点。再考虑空间复杂度,显然我们开不下1000 * 1000 * 1000的数组,但是由上述结论我们可以发现,当枚举时间点到i时,dp1~ i-210,1~ j-210,1~ k-210的值已经完全没有用了,我们可以用类似循环队列的方式优化这些空间,于是,我们只需开大概210 * 210 * 210的空间就足够了。
一些细节问题:
- 初始化dp数组时不能无脑memset,因为T=1e3,会超时,正确做法是初始化下标0~ min(n,210)的dp数组。
- 时限卡得比较紧,尽量不要分成很多个三重循环来写,能用int不要用long long。
- 注意枚举0这一特殊情况,下标为0的dp数组不能被覆盖!
```#include<bits/stdc++.h>
#include<queue>
#include<string.h>
#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define nl tree[n].l
#define nr tree[n].r
#define gcd __gcd
using namespace std;
//#pragma GCC optimize(2)
const int maxn=1e6+50;
const int inf=1e18;
const int mod=1e9+7;
const int INF=2e18;
const double eps=1e-8;
int a[1005],b[1005],c[1005];
int dp[210][210][210];
void solve()
{
int n;
cin>>n;
rep(i,1,n) cin>>a[i]>>b[i]>>c[i];
int lim=min(205,n);
rep(i,0,lim)
{
rep(j,0,lim)
{
rep(k,0,lim) dp[i][j][k]=0;
}
}
dp[1][0][0]=a[1];
dp[0][1][0]=b[1];
dp[0][0][1]=c[1];
rep(i,2,n)
{
int st=max(1,i-203);
int nowi=(i-1)%205+1;
int lasti=(i-2)%205+1;
rep(j,0,i-2)
{
int nowj=(j-1)%205+1;
rep(k,0,i-2)
{
if(j==k&&j) continue;
int nowk=(k-1)%205+1;
if(j==0&&k==0)
{
dp[nowi][nowj][nowk]=max(dp[nowi][nowj][nowk],a[i]+dp[lasti][nowj][nowk]);
dp[nowj][nowi][nowk]=max(dp[nowj][nowi][nowk],b[i]+dp[nowj][lasti][nowk]);
dp[nowj][nowk][nowi]=max(dp[nowj][nowk][nowi],c[i]+dp[nowj][nowk][lasti]);
}
else if(j==0)
{
dp[nowi][nowj][nowk]=max(dp[nowi][nowj][nowk],a[i]+dp[lasti][nowj][nowk]-(i-k));
dp[nowj][nowi][nowk]=max(dp[nowj][nowi][nowk],b[i]+dp[nowj][lasti][nowk]-(i-k));
dp[nowj][nowk][nowi]=max(dp[nowj][nowk][nowi],c[i]+dp[nowj][nowk][lasti]-(i-k));
}
else if(k==0)
{
dp[nowi][nowj][nowk]=max(dp[nowi][nowj][nowk],a[i]+dp[lasti][nowj][nowk]-(i-j));
dp[nowj][nowi][nowk]=max(dp[nowj][nowi][nowk],b[i]+dp[nowj][lasti][nowk]-(i-j));
dp[nowj][nowk][nowi]=max(dp[nowj][nowk][nowi],c[i]+dp[nowj][nowk][lasti]-(i-j));
}
else
{
dp[nowi][nowj][nowk]=max(dp[nowi][nowj][nowk],a[i]+dp[lasti][nowj][nowk]-(i-j)-(i-k));
dp[nowj][nowi][nowk]=max(dp[nowj][nowi][nowk],b[i]+dp[nowj][lasti][nowk]-(i-j)-(i-k));
dp[nowj][nowk][nowi]=max(dp[nowj][nowk][nowi],c[i]+dp[nowj][nowk][lasti]-(i-j)-(i-k));
}
if(k==0)
{
dp[nowi][lasti][nowk]=max(dp[nowi][lasti][nowk],a[i]+dp[nowj][lasti][nowk]-1);
dp[nowi][nowk][lasti]=max(dp[nowi][nowk][lasti],a[i]+dp[nowj][nowk][lasti]-1);
dp[lasti][nowi][nowk]=max(dp[lasti][nowi][nowk],b[i]+dp[lasti][nowj][nowk]-1);
dp[nowk][nowi][lasti]=max(dp[nowk][nowi][lasti],b[i]+dp[nowk][nowj][lasti]-1);
dp[lasti][nowk][nowi]=max(dp[lasti][nowk][nowi],c[i]+dp[lasti][nowk][nowj]-1);
dp[nowk][lasti][nowi]=max(dp[nowk][lasti][nowi],c[i]+dp[nowk][lasti][nowj]-1);
k=st-1;
}
else
{
dp[nowi][lasti][nowk]=max(dp[nowi][lasti][nowk],a[i]+dp[nowj][lasti][nowk]-1-(i-k));
dp[nowi][nowk][lasti]=max(dp[nowi][nowk][lasti],a[i]+dp[nowj][nowk][lasti]-1-(i-k));
dp[lasti][nowi][nowk]=max(dp[lasti][nowi][nowk],b[i]+dp[lasti][nowj][nowk]-1-(i-k));
dp[nowk][nowi][lasti]=max(dp[nowk][nowi][lasti],b[i]+dp[nowk][nowj][lasti]-1-(i-k));
dp[lasti][nowk][nowi]=max(dp[lasti][nowk][nowi],c[i]+dp[lasti][nowk][nowj]-1-(i-k));
dp[nowk][lasti][nowi]=max(dp[nowk][lasti][nowi],c[i]+dp[nowk][lasti][nowj]-1-(i-k));
}
}
if(j==0) j=st-1;
}
}
int ans=0;
int nown=(n-1)%205+1;
rep(i,0,lim)
{
rep(j,0,lim)
{
if(i==j&&i) continue;
ans=max({ans,dp[nown][i][j],dp[i][j][nown],dp[i][nown][j]});
}
}
cout<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int __=1;
cin>>__;
while(__--)
{
solve();
}
return 0;
}