动态规划杂题(2200-2500)

CF1859E

有两个长度为 n 的序列 ab。其中区间 [l,r](1lrn) 的价值是 |blar|+|bral|

区间 [l1,r1] (1l1r1n) 和区间 [l2,r2] (1l2r2n) 不相交,是指 r1<l2 满足或 r2<l1 满足。

区间 [l,r] (1lrn) 的长度被定义为 rl+1

给定 a,b,求若干个互不相交的,总长度为 k [l,r] 的价值总和的最大值。

思路点拨

我们考虑一个 O(nk2) 的暴力动态规划。我们定义状态 fi,j 表示目前考虑到 i ,选了的长度为 j 的最大值。转移分两类讨论:

  • 不选择 i , 那么 fi,j=fi1,j

  • 选择 i ,枚举 i 所属这一段的长度, fi,j=maxmid=1k{fimid,jmid+|bmid+1ai|+|biamid+1|}

考虑优化,这里需要运用一种动态规划中常用的技巧:拆分绝对值。

我们将转移分为四类讨论:

fi,j=fimid,jmid+bmid+1ai+biamid+1

fi,j=fimid,jmidbmid+1+ai+biamid+1

fi,j=fimid,jmid+bmid+1aibi+amid+1

fi,j=fimid,jmidbmid+1+aibi+amid+1

Q:这样难道就不需要考虑 al,ar,bl,br 的大小关系吗?不会算多或者算少吗?

A:并不会,因为绝对值有性质:X|X| 。所以我们这样计算肯定包含最优解并且不会多计算贡献只会少计算(不影响答案)。

对于上述的转移,我们将包含 ai,bi 的式子合并,整理得到:

fi,j=fimid,jmid+(biai)+(bmid+1amid+1)

fi,j=fimid,jmid+(ai+bi)(bmid+1+amid+1)

fi,j=fimid,jmid(ai+bi)+(bmid+1+amid+1)

fi,j=fimid,jmid+(aibi)+(amid+1bmid+1)

因为 aibi 一类的式子是必须要加上的,我们无须考虑,对于剩下的部分,可以记录 dplen,0/1/2/3 作为前缀最大值优化转移。

    for(int i=1;i<=n;i++)
        for(int j=1;j<=min(i,k);j++){
            dp[i][j]=dp[i-1][j];
            f[i-j][0]=max(f[i-j][0],dp[i-1][j-1]+a[i]+b[i]);
            f[i-j][1]=max(f[i-j][1],dp[i-1][j-1]-a[i]+b[i]);
            f[i-j][2]=max(f[i-j][2],dp[i-1][j-1]+a[i]-b[i]);
            f[i-j][3]=max(f[i-j][3],dp[i-1][j-1]-a[i]-b[i]);
            dp[i][j]=max(dp[i][j],f[i-j][0]-a[i]-b[i]);
            dp[i][j]=max(dp[i][j],f[i-j][1]-a[i]+b[i]);
            dp[i][j]=max(dp[i][j],f[i-j][2]+a[i]-b[i]);
            dp[i][j]=max(dp[i][j],f[i-j][3]+a[i]+b[i]);
        }

CF1858D

现在有一个长度为 n 的序列,每一个位置山过的元素 ai{0,1} 。现在你有至多 k 次机会修改一个位置上的值, 0 变成 11 变成 0 。假设 k 次操作之后最长的连续 1 的长度为 l1, 最长的连续 0 长度为 l0 。对于每一个常数 z[1,n] ,求出 z×l0+l1 的最大值。

思路点拨

发现产生贡献的两端 0 或者 1 不会相交,所以我们可以记录前缀和后缀的信息。定义状态:

  • pre0[i][j] 表示考虑到前缀 i ,修改了 j 次之后最长 0 可以是多少。

  • pre1[i][j] 表示考虑到前缀 i ,修改了 j 次之后最长 1 可以是多少。

  • suc0[i][j] 表示考虑到后缀 i ,修改了 j 次之后最长 0 可以是多少。

  • suc1[i][j] 表示考虑到后缀 i ,修改了 j 次之后最长 1 可以是多少。

如果可以求出来,最后通过一些手段合并答案就可以了。

转移也比较简单:

pre0[i][j]=pre0[i1][j[ai=1]]+1

pre1[i][j]=pre1[i1][j[ai=0]]+1

suc0[i][j]=suc0[i+1][j[ai=1]]+1

suc1[i][j]=suc1[i+1][j[ai=0]]+1

我们可以对上述四个数组做二维前缀最大值,数组的意义将转变为:

  • pre0[i][j] 表示考虑到前缀 1,2,...i ,修改了 j 次之后最长 0 可以是多少。

  • pre1[i][j] 表示考虑到前缀 1,2,...i ,修改了 j 次之后最长 1 可以是多少。

  • suc0[i][j] 表示考虑到后缀 i,i+1,...,n ,修改了 j 次之后最长 0 可以是多少。

  • suc1[i][j] 表示考虑到后缀 i,i+1,...,n ,修改了 j 次之后最长 1 可以是多少。

现在我们如果枚举一个常数 z ,再枚举一个最长 0 和最长 O(n3) 的时间求出解。但是我们发现这个过程十分的重复,我们考虑优化。

定义 mxi 表示当最长 l0i 时, l1 的最大可能值。转移显然:

mxpre0[i][j]=max{suc1[i+1][kj]}

mxsuc0[i+1][j]=max{pre1[i][kj]}

可以看见,转移的时间等于 pre0,suc0 的状态数量,也就是 O(n2)

每一次我们枚举一个常数 z ,答案就是 maxi=1kmxi+i×z

		for(int i=1;i<=n;i++){
			pre0[i][0]=(a[i]==1)?0:pre0[i-1][0]+1;
			for(int j=1;j<=min(i,k);j++)
				pre0[i][j]=(a[i]==1)?pre0[i-1][j-1]+1:pre0[i-1][j]+1;
			pre1[i][0]=(a[i]==0)?0:pre1[i-1][0]+1;
			for(int j=1;j<=min(i,k);j++)
				pre1[i][j]=(a[i]==0)?pre1[i-1][j-1]+1:pre1[i-1][j]+1;
		}
		for(int i=n;i;i--){
			suc0[i][0]=(a[i]==1)?0:suc0[i+1][0]+1;
			for(int j=1;j<=min(n-i+1,k);j++)
				suc0[i][j]=(a[i]==1)?suc0[i+1][j-1]+1:suc0[i+1][j]+1;
			suc1[i][0]=(a[i]==0)?0:suc1[i+1][0]+1;
			for(int j=1;j<=min(n-i+1,k);j++)
				suc1[i][j]=(a[i]==0)?suc1[i+1][j-1]+1:suc1[i+1][j]+1;
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<=k;j++){
				pre0[i][j]=max(pre0[i][j],pre0[i-1][j]);
				pre1[i][j]=max(pre1[i][j],pre1[i-1][j]);
			}
		}
		for(int i=n;i;i--){
			for(int j=0;j<=k;j++){
				suc0[i][j]=max(suc0[i][j],suc0[i+1][j]);
				suc1[i][j]=max(suc1[i][j],suc1[i+1][j]);
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=k;j++){
				pre0[i][j]=max(pre0[i][j],pre0[i][j-1]);
				pre1[i][j]=max(pre1[i][j],pre1[i][j-1]);
				suc0[i][j]=max(suc0[i][j],suc0[i][j-1]);
				suc1[i][j]=max(suc1[i][j],suc1[i][j-1]);
			}
		}
		memset(mx,-1,sizeof(mx));
		for(int i=1;i<=n;i++){
			for(int j=0;j<=k;j++)
				mx[pre0[i][j]]=max(mx[pre0[i][j]],suc1[i+1][k-j]);
			for(int j=0;j<=k;j++)
				mx[suc0[i][j]]=max(mx[suc0[i][j]],pre1[i-1][k-j]);
		}
		mx[0]=max(mx[0],pre1[n][k]);
		for(int i=1;i<=n;i++){
			int ans=0;
			for(int j=0;j<=n;j++)
				if(mx[j]!=-1) ans=max(ans,j*i+mx[j]);
			cout<<ans<<" ";
		}

CF1854C

给定大小为 n 的正整数集合 SS 中的每个数在 1m 之间。

每一秒进行如下操作:

  1. S 中等概率随机选择一个数 x
  2. xS 中删去。
  3. x+1mx+1S,则将 x+1 加入 S

S 变成空集的期望时间,对 109+7 取模。

1nm5001S1<S2<<Snm

思路点拨

如果去除第三点限制中的 x+1S ,那么答案就是 (mSi+1) 。现在考虑如果有这种情况怎么办。因为 1S1<S2<<Snm ,所以如果发生 x+1S 的情况,也只会在相邻两项之间发生。根据期望的线性性质,我们可以对于相邻两项分别考虑,对于 Si,Si+1

我们定义状态 fa,b 表示 Si=a,Si+1=b 的概率是多少,边界条件就是 fSi.Si+1=1 。转移填表刷表都可以,这里给出刷表:

fa+1,b=fa+1,b+fa,b×12

fa,b+1=fa,b+1+fa,b×12

这里讲一下为什么只乘上 12 ,难道不需要考虑其他元素的选择吗?我们讲到,我们根据线性性质转化为了相邻两项之间的贡献,那么这个子问题不需要考虑其余元素,由于 Si,Si+1 会被等概率选中,所以乘上 12Si,Si+1 的贡献就是 fi,j×(mi+1) 。这个时候一个元素 S=i 原来会产生 (mi+1) 的贡献,但是因为他与另一个元素相同了,所以不可以继续产生贡献,被我们多算了。

现在的时间复杂度是 O(nm2) ,的确可以通过,但是我们有更加优秀的做法。还是由于期望的线性性质,我们可以将全部的动态规划一起运算,并不会引起答案错误,时间复杂度就是 O(m2)

posted @   Diavolo-Kuang  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示