CF1987F2 题解
题意
给你一个长度为n的数组,每次你可以选择一个 \(a_i=i\) 的位置,把 \(a_i,a_{i+1}\) 删掉,求最多能删多少次。
题解
我们考虑设 \(dp_{l,r}\) 表示如果想要把 \(l\) 到 \(r\) 区间内的数全部删掉,最少要在前面删掉多少个。
那么转移是好转移的,它要么是两段合起来,要么是把中间删掉之后删掉 \(l\) 和 \(r\) 位置上的数。
然后你考虑怎么求答案。
显然,你每次都是尽可能删掉最末尾的最多的数。
所以,我们设 \(f_{i,j}\) 表示前 \(i\) 个数,能否删 \(j\) 次。
然后你每次只需要往前枚举最后删掉的那一段,假设删掉 \(l1,r1\) 这一段最少要在前面删 \(k\) 次,那么,你就看 \(f_{l1-1,k}\),如果他是可行的,那么往后删掉这一段就也是可行的。
然后你就做完了,最后找到最大的等于 \(1\) 的 \(f_{n,i}\) 输出即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
const int N=880;
int dp[N][N],a[N],f[N][N];
int read() {int x;cin>>x;return x;}
void work() {
n=read();
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++) {
a[i]=read();
// if(a[i]==i) dp[i][i+1]=0;
dp[i][i-1]=0;
// if(i<n&&a[i]%2==i%2&&a[i]<i) {
// dp[i][i+1]=min(dp[i][i+1],(i-a[i])/2);
// }
}
dp[n][n]=dp[n][n+1]=1e9;
for(int len=2;len<=n;len+=2) {
for(int l=1,r=len;r<=n;l++,r++) {
for(int k=l;k<=r;k+=2) {
if(a[k]%2!=k%2||a[k]>k) continue;
int psl=max(dp[l][k-1],(l-a[k])/2);
if((k-a[k])/2>=dp[k+1][r-1]) dp[l][r]=min(dp[l][r],psl);
}
// if(a[l]%2==l%2&&a[l]<=l) dp[l][r]=min(dp[l][r],max((l-a[l])/2,dp[l+1][r-1]));
}
}
memset(f,0,sizeof(f));
f[0][0]=1;
// for(int i=1;i<=n;i++) {
// for(int j=i+1;j<=n;j++) {
// if(dp[i][j]<n) cerr<<i<<" "<<j<<" "<<dp[i][j]<<endl;
// }
// }
for(int i=1;i<=n;i++) {
for(int j=0;j<=n;j++) {f[i][j]=f[i-1][j];}
for(int j=1;j<=i;j++) {
if((i-j+1)%2==0) {
int t=dp[j][i];
for(int k=t;k<=n;k++) {
if(f[j-1][k]) f[i][k+(i-j+1)/2]=1;
}
}
}
}
for(int i=n;i>=0;i--) {
if(f[n][i]) {
cout<<i<<"\n";
return;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t=read();
while(t--) work();
return 0;
}

浙公网安备 33010602011771号