牛客周赛138补题
E 小苯的数字染色
知识点:线性dp

手模样例发现,贪心是不行的,所以我们考虑dp,先考虑线性dp,如果用 dp[i] 来表示到第i个元素能获得的最大分数,那么对于随便一个dp[i]来说,进行状态转移,如果当前数你不取得话,直接把 dp[i-1] 的数值拿过来就可以了,如果取得话,就要在 1~i-1 之间找一个同奇偶的数来配对,所以枚举j的位置,这样状态转移方程就是 dp[i]=max(dp[i-1],dp[j-1]+a[j]+a[i]])
但是注意到n是1e5的,枚举j就是 O(n^2) 的时间复杂度,会TLE,所以想想如何优化,看看状态转移方程,发现dp[j-1]+a[j]我们在遍历i的过程中可以顺手维护,但这就很棒了,只需要奇数偶数分别维护一个j的最大值就行了。
code
void solve()
{
cin>>n;
vi a(n+1);
int ans=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vi dp(n+1,0);
int ji=-1e9;
int ou=-1e9;
for(int i=1;i<=n;i++)
{
if(a[i]&1)
{
dp[i]=max(dp[i-1],a[i]+ji);
ji=max(ji,dp[i-1]+a[i]);
}
else
{
dp[i]=max(dp[i-1],a[i]+ou);
ou=max(ou,dp[i-1]+a[i]);
}
}
cout<<dp[n]<<endl;
}
F 小苯的对称序列
知识点:区间DP

这是一个区间问题,然后发现贪心不可做,考虑DP
如果是线性DP,状态难以维护,也不是背包,和树也没关系,考虑区间DP
发现n的数据范围非常小,支持O(\(n{3}\))的操作,所以区间DP可以尝试。
传统的区间dp,都是枚举长度和左端点然后枚举中间一个断点,但在这里发现如果这样,发现会少一个状态没有维护到,那就是与中间断点相对应的右端点,所以想办法预处理这个东西
对于l,j,r来说,右端点的数字应该是a[r]+a[l]-a[j],如果j和r之间如果有多个这样的数字,显而易见我们应该选更靠右的
那么我们就预处理对于每个i来说每个值在i左边的位置,这样就解决了一个状态
注意:不要忘了预处理
如何预处理呢?如果长度为1的子序列,dp值肯定为1,又因为这个子序列是不需要连续的,所以对于任意两个不同位置的值来说,dp值都应该是2
然后再进行传统的区间DP,枚举的长度应该从3开始
void solve()
{
cin>>n;
vi a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vvi dp(n+1,vi(n+1,0));
for(int i=1;i<=n;i++)
{
dp[i][i]=1;
for(int j=i+1;j<=n;j++)
{
dp[i][j]=2;
}
}
vvi nex(n+1,vi(1001,0));
for(int i=1;i<=n;i++)
{
nex[i]=nex[i-1];
if(i>1)
{
nex[i][a[i-1]]=i-1;
}
}
for(int len=3;len<=n;len++)
{
for(int l=1;l+len-1<=n;l++)
{
r=l+len-1;
for(int j=l+1;j<r;j++)
{
int temp=a[r]+a[l]-a[j];
if(temp<1||temp>1000)
{
continue;
}
dp[l][r]=max(dp[l][r],2+dp[j][nex[r][temp]]);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
ans=max(ans,dp[i][j]);
}
}
cout<<ans<<endl;
}

浙公网安备 33010602011771号