DP problems

DP problems (3)

Codeforces Round 893 (Div. 2) D

题意

  有一个长度为 n n n 01 01 01串,反转其中一个元素( 0 0 0变为 1 1 1,或者 1 1 1变为 0 0 0)称为一次操作,我们可以对它进行不超过 k k k次操作, l 0 l_0 l0表示字符串中最长的连续的 0 0 0的长度, l 1 l_1 l1表示字符串中最长的连续的 1 1 1的长度,我们需要输出 n n n个数,第 i i i个表示对原串操作后 i ∗ l 0 + l 1 i*l_0+l_1 il0+l1的最大值。

解法

  经过分析发现,结果中会存在某个分界点,分界点左边全为 0 0 0变为 1 1 1的操作,右边全为 1 1 1变为 0 0 0的操作(或左边全为 1 1 1变为 0 0 0的操作,右边全为 0 0 0变为 1 1 1操作),于是我们用 p r e [ i ] [ j ] [ k ] pre[i][j][k] pre[i][j][k]表示前 j j j个数进行不超过 k k k次操作后连续的 i i i的最长的长度, s u f [ i ] [ j ] [ k ] suf[i][j][k] suf[i][j][k]表示后缀。对于要输出的第 i i i个数,我们二重循环枚举分界点和操作次数,得到一个 O ( n 3 ) O(n^3) O(n3)的算法。
  我们需要再进行优化,可以发现答案只与 l 0 l_0 l0 l 1 l_1 l1有关,我们并不关心分界点的位置,于是我们处理出中间数组 f f f f [ i ] f[i] f[i]表示 l 0 l_0 l0 i i i时, l 1 l_1 l1最大为多少,于是对于要输出的第 i i i个数,我们只需要枚举 l 0 l_0 l0即可。此时的复杂度为 O ( n 2 ) O(n^2) O(n2)。注意不要#define int long long,这样会MLE,不过下面的方法不会。
  还有一种计算 f f f 数组的方法,我们二重循环枚举最长的连续的 0 0 0的起始位置再用 p r e pre pre s u f suf suf数组 O ( 1 ) O(1) O(1)求出此时的 l 1 l_1 l1

inline void solve(){
	cin>>n>>k;
	string s;cin>>s;s=" "+s;
	for(int i=1;i<=n;i++)a[i]=s[i]-'0';
	for(int i=1;i<=n;i++)a[i]+=a[i-1];
	
	for(int j=0;j<=k;j++){
		int l=1;
		for(int i=1;i<=n;i++){//1->0
			while(l<=i&&a[i]-a[l-1]>j)l++;
			pre[0][i][j]=max(pre[0][i-1][j],i-l+1);
		}
		l=1;
		for(int i=1;i<=n;i++){//0->1
			while(l<=i&&i-l+1-(a[i]-a[l-1])>j)l++;
			pre[1][i][j]=max(pre[1][i-1][j],i-l+1);
		}
	}
	for(int j=0;j<=k;j++){
		suf[0][n+1][j]=suf[1][n+1][j]=0;
		int r=n;
		for(int i=n;i>=1;i--){//1->0
			while(r>=i&&a[r]-a[i-1]>j)r--;
			suf[0][i][j]=max(suf[0][i+1][j],r-i+1);
		}
		r=n;
		for(int i=n;i>=1;i--){//0->1
			while(r>=i&&r-i+1-(a[r]-a[i-1])>j)r--;
			suf[1][i][j]=max(suf[1][i+1][j],r-i+1);
		}
	}
	
	vector<int>f(n+1,-inf);
	for(int j=0;j<=k;j++){
		for(int i=0;i<=n;i++){
			for(int t=0;t<=1;t++){
				int len1=pre[t][i][j],len2=suf[t^1][i+1][k-j];
				if(!t)f[len1]=max(f[len1],len2);
				else f[len2]=max(f[len2],len1);
			}
		}
	}
	
	for(int i=n-1;i>=0;i--)f[i]=max(f[i],f[i+1]);
	
	for(int j=1;j<=n;j++){
		ans[j]=0;
		for(int i=0;i<=n;i++){
			ans[j]=max(ans[j],j*i+f[i]);
		}
	}
	for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
	cout<<'\n';
}

Educational Codeforces Round 153 (Rated for Div. 2) D

题意

  当一个 01 01 01串中子序列 01 01 01的数量等于子序列 10 10 10的数量时,我们称它是平衡的,交换字符串中的两个元素称为一次操作,我们要求出使当前字符串变成平衡的所需要的最少操作次数。

解法

  容易知道只有 0 0 0 1 1 1交换才有用,我们可以把一次操作看为同时反转两个不同位置的字符,但是这样不好写 d p dp dp
  我们观察到操作时, 0 0 0 1 1 1的个数是不变的,我们不妨定义新操作为只反转一个位置的元素,最后通过限制操作之后 0 0 0 1 1 1的个数来求出结果,最后得到的操作数除以 2 2 2便是答案。所以我们定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示前 i i i位,有 j j j 1 1 1, 01 01 01 10 10 10 k 个 k个 k时的最少操作数即可,答案为 d p [ n ] [ c n t 1 ] [ 0 + d l t ] / 2 dp[n][cnt1][0+dlt]/2 dp[n][cnt1][0+dlt]/2(其中 c n t 1 cnt1 cnt1表示原串中 1 1 1的个数, d l t dlt dlt表示偏移量)。注意需要滚动优化掉第一维, k k k可能为负数,需要加一个偏置量。

Educational Codeforces Round 154 (Rated for Div. 2) E

题意

  给出 n n n k k k,定义一个数组的花费如下:
  将数组分为若干子数组,每个数属于最多一个子数组,所有划分方案中是 k k k的排列的子数组的最大数量被称为这个数组的花费。
  求出所有长度为 n n n且元素的值在 1 1 1 k k k之间的数组的花费之和。

解法
  • 方法一
    定义 d p [ i ] [ j ] [ t ] dp[i][j][t] dp[i][j][t]表示长度为 i i i的数组结尾有 j j j个互不相同的数,且前 i − j i-j ij个数的花费是 t t t(这个dp数组只有 O ( n 2 ) O(n^{2}) O(n2)个状态),转移的时候用差分优化可以做到 O ( n 2 ) O(n^{2}) O(n2)的复杂度
void solve() {
	int n,k;cin>>n>>k;
	vector<vector<vector<int>>>dp(n+1,vector(k+1,vector(n/k+2,0))); 
	dp[0][0][0] = 1;
	for(int i=0;i<n;i++) {
		for(int t = 0;t<=i/k;t++) {
			vector<int>tmp(k+1,0);
			for(int j=0;j<k;j++) {
				tmp[j]=(tmp[j]+dp[i][j][t])%mod;
				
				if(j==k-1)dp[i+1][0][t+1]=(dp[i+1][0][t+1]+dp[i][j][t])%mod;
				else dp[i+1][j+1][t]=(dp[i+1][j+1][t]+dp[i][j][t]*(k-j)%mod)%mod;
			}
			
			for(int j = k - 1; j >= 1; j--) {
				dp[i+1][j][t]=(dp[i+1][j][t]+tmp[j])%mod;
				tmp[j - 1]=(tmp[j-1]+tmp[j])%mod;
 			}
		}

	}
	int ans=0;
	for(int j=0;j<k;j++) {
		for(int t=0;t<=n/k;t++) {
			ans=(ans+t*dp[n][j][t]%mod)%mod;
		}
	}
	cout<<ans<<'\n';
}
  • 方法二
    link

你可能感兴趣的:(算法)