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;
}
posted @ 2025-10-24 15:04  wjx_2010  阅读(9)  评论(0)    收藏  举报