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';
}
### Bitmask 动态规划练习题及其解法 Bitmask 动态规划是一种结合了位运算和动态规划的思想方法,适用于状态数量有限但复杂度较高的问题。以下是几个经典的 bitmask DP 问题及其实现思路。 --- #### **经典题目 1**: LeetCode 698. Partition to K Equal Sum Subsets 给定一个整数数组 `nums` 和正整数 `k`,判断能否将这个数组分成 `k` 个子集,使得每个子集的和相同。 ##### 解法分析 该问题可以通过 bitmask 来表示当前已经分配到哪些元素的状态。定义 `dp[mask]` 表示是否能够通过某些组合形成目标和的部分集合。初始条件为 `dp[0] = true`,即没有任何元素被选中的情况下是可以满足条件的[^2]。 代码实现如下: ```java public boolean canPartitionKSubsets(int[] nums, int k) { int totalSum = Arrays.stream(nums).sum(); if (totalSum % k != 0 || k > nums.length) return false; int target = totalSum / k; int n = nums.length; boolean[] dp = new boolean[1 << n]; dp[0] = true; int[] sums = new int[1 << n]; // 记录每种状态下已有的总和 for (int mask = 0; mask < (1 << n); ++mask) { if (!dp[mask]) continue; for (int i = 0; i < n; ++i) { if (((mask >> i) & 1) == 0 && sums[mask] + nums[i] <= target) { int nextMask = mask | (1 << i); if (!dp[nextMask]) { dp[nextMask] = true; sums[nextMask] = (sums[mask] + nums[i]) % target; if (nextMask == (1 << n) - 1) return true; } } } } return dp[(1 << n) - 1]; } ``` --- #### **经典题目 2**: Codeforces Problem C. Maximum XOR Subset 找到一个大小不超过 `m` 的子集的最大异或值。 ##### 解法分析 利用 bitmask 枚举所有可能的子集,并计算其对应的异或值。由于最多只有 \(O(2^n)\) 种可能性,在合理范围内可以直接枚举并记录最大值[^3]。 代码实现如下: ```cpp #include <bits/stdc++.h> using namespace std; int main() { int n, m; cin >> n >> m; vector<int> a(n); for(auto& x : a) cin>>x; int max_xor = 0; for(int mask=0; mask<(1<<n); ++mask){ if(__builtin_popcount(mask)>m) continue; // 跳过超过限制的情况 int current_xor = 0; for(int j=0; j<n; ++j){ if((mask&(1<<j))!=0){ current_xor ^=a[j]; } } max_xor = max(max_xor, current_xor); } cout << max_xor; } ``` --- #### **经典题目 3**: AtCoder ABC D-Level Problems (e.g., Traveling Salesman Problem with Constraints) 旅行商问题变体:在一个图中访问若干节点一次,返回最短路径长度。 ##### 解法分析 使用 bitmask 存储当前访问过的城市集合,转移方程为: \[ dp[mask][u] = \min(dp[mask'][v] + dist[v][u]) \] 其中 `mask'` 是去掉当前城市的前缀状态,`dist[v][u]` 表示从城市 `v` 到城市 `u` 的距离[^4]。 代码框架如下: ```python from functools import lru_cache def tsp(graph): N = len(graph) INF = float('inf') @lru_cache(None) def dfs(mask, u): # 当前状态和当前位置 if mask == (1 << N) - 1 and u == 0: # 所有城市都访问过了且回到了起点 return graph[u][0] or INF min_cost = INF for v in range(N): if not (mask & (1 << v)): # 如果未访问过此城市 cost = graph[u][v] or INF min_cost = min(min_cost, cost + dfs(mask | (1 << v), v)) return min_cost return dfs(1, 0) # Example usage: graph = [ [0, 10, 15, 20], [10, 0, 35, 25], [15, 35, 0, 30], [20, 25, 30, 0] ] print(tsp(graph)) ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值