插入类dp

按结尾数字排名进行的插入类dp

T1 AT_dp_t Permutation

有一个长为 N 的正整数排列。给定一个由 <> 组成长为 N1 的的字符串。
对于任意满足 1iN1 的字符 si,如果 si<Pi<Pi+1、如果 si>Pi>Pi+1。求满足这样的性质的排列 P 的方案数。

N3000

考虑动态规划

f[i][j]表示对于前i个数字,最后一个数字在前i个数字里排名第j的方案数。

比如样例4 <><

f[1][1]=1,f[2][1]=0,f[2][2]=1

1 2

f[3][1]=1,f[3][2]=1,f[3][3]=0

2 3 1,1 3 2

f[4][1]=0,f[4][2]=1,f[4][3]=2,f[4][4]=2

3 4 1 2,1 4 2 3,2 4 1 3,1 3 2 4,2 3 1 4

if(s[i-1]=='<')

f[i][j]=k=1j1f[i1][k]

else

f[i][j]=k=jk=i1f[i1][k]

前缀和优化之。

T2 AT_abc209_f Deforestation

n 个数:a1,a2,...,an

每次可以选择一个 i,选择的代价是 ai1+ai+ai+1,然后令 ai=0

求有多少种方案,使得 a1,a2,...,an 都变为 0 的总代价最小。特别的,a0=an+1=0

1n4000,1ai109

考虑这个限制是什么。

对于ii+1

先拿i再拿i+1,收益是a[i]+2a[i+1]

先拿i+1再拿i,收益是a[i+1]+2a[i]

显然,哪个大我希望先拿哪个。如果相等就随意。

于是和T1一样了

if(a[i1]>a[i])

f[i][j]=k=1j1f[i1][k]

else if(a[i1]<a[i])

f[i][j]=k=jk=i1f[i1][k]

else

f[i][j]=k=1i1f[i1][k]

按段数进行的插入类dp

T3 P5999 kangaroo

有一个园子,里面有 n 个草丛排成一排,标号 1n,有一个袋鼠,从 s 出发,每次跳一步跳到一个其他的草丛,经过每个草丛恰好一次,最终到达 t。显然他会跳跃 n1次为了不被人类发现,袋鼠每次跳跃的方向必须与前一次不同。

具体地,如果他现在在 now,他是从 prev 跳跃一次到达 now 的,然后他跳跃一次到达 next

  • 那么如果 prev<now,就必须有 next<now

  • 如果 now<prev,就必须有 now<next

问从 st 的方案数模 109+7的结果。

两个路线不同,当且仅当草丛被访问的顺序不同。

保证至少有一种方案初始时可以往任意方向跳。

2n2×1031s,tn

f[i][j]表示前i个数字,分成了j段。我们规定每段都是一个元素或者低高低或者低高低高低或者低高低高低高低...

那么处理f[i][j]时,i对于前i1个数字都是高的,所以他可以用来合并两个段,或者新开一段

f[i][j]=f[i1][j+1]j+f[i1][j1]j

要求的序列可能是高低高低啊?我们接着往下看

s和t的关系如何解决?

考虑当i==s时,令f[i][j]=f[i1][j1]+f[i1][j]。也就是新开一段放在最前,或者放在最前面的段的最前面。

考虑当i==t时,令f[i][j]=f[i1][j1]+f[i1][j]。也就是新开一段放在最后,或者放在最前面的段的最后面。

放下来之后,我们修改普通的i的新开一个段的转移:

i!=s&&i!=t

f[i][j]=f[i1][j+1]j+f[i1][j1](j(i>s)(i>t))

也就是说,合并两个段的方案数不变,新开一段时,如果比st都大,就只能从中间新开(j-2)。如果大于s小于t,则可以在中间和最后新开(j-1)。如果小于s也小于t,就可以从中间和两边开(j)。

最后输出f[n][1]

T4 CF704B Ant Man

  • n 个元素,第 i 个元素有五个参数 xi,ai,bi,ci,di
  • 你需要求出一个 1n 的排列 p,满足 p1=s,pn=e,同时最小化这个排列的权值。
  • 一个排列的权值为 i=1n1f(pi,pi+1),其中 f(i,j) 的值有两种情况:
    • i>j,则 f(i,j)=xixj+ci+bj
    • i<j,则 f(i,j)=xjxi+di+aj
  • n5×103se1x1<x2<<xn1091ai,bi,ci,di109

f[i][j]表示前i个数字,有j段的最小花费。

f[0][1]=0

普通的情况

f[i][j]=f[i1][j1]x[i]2+b[i]+d[i] 新开一段。

这一段必然是在未来作为高低高的低出现的,所以他对答案的贡献是-2x+b+d。

f[i][j]=f[i1][j+1]+x[i]2+a[i]+c[i]合并两个段。

这个点作为低高低的高出现,对答案的贡献是2x+c+a。

f[i][j]=f[i1][j]+b[i]+c[i]往某个段前面放。

那他作为高中低的中出现,对答案的贡献是b+c。

f[i][j]=f[i1][j]+a[i]+d[i]往某个段后面放。

那他作为低中高的中出现,对答案的贡献是a+d。

对于起点

f[i][j]=f[i1][j]+x[i]+c[i]

连接在某个段前面,于是最终排列高低起手,s的贡献是x+c。

f[i][j]=f[i1][j1]x[i]+d[i]

新开一个段,于是最终排列低高起手,s的贡献是-x+d。

对于终点

f[i][j]=f[i1][j]+x[i]+a[i]

连接在某个段的后面,于是最终排列低高结尾,t的贡献是x+a。

f[i][j]=f[i1][j1]x[i]+b[i]

新开一个段,于是最终排列高低结尾,t的贡献是-x+b。

考虑起点和终点固定下来对于普通的情况的转移有什么影响?

影响就是

想新开一段时,不能i>s&&i>t&&j1==1,此时st做起点终点,只有一段,就不能新开一段了。

想往某个段的前面放,要么s没出现,要么s出现了并且段数大于1才能往前放。

想往某个段的后面放,要么t没出现,要么t出现了并且段数大于1才能往后放。

输出f[n][1]

T5 P9197 摩天大楼

为了规避掉绝对值,我们可以排个序,就和上一题类似了

这题不规定左右端点了,考虑dp的时候加一维

f[i][j][x][sum]表示前i个数字,有j段,有x个端点,和为sum

if(j>=x)
	f[i][j][x][sum]+=f[i-1][j-1][x][sum+2*a[i]]*(j-x);//新开一个,放在任意位置,贡献为-2ai
f[i][j][x][sum]+=f[i-1][j][x][sum]*(2*j-x);//放在某一段的左右,贡献为0
f[i][j][x][sum]+=f[i-1][j+1][x][sum-2*a[i]]*j;//合并两个,放在任意位置,贡献为2ai
if(x)
{
	f[i][j][x][sum]+=f[i-1][j-1][x-1][sum+a[i]]*(3-x);//让ai新开一段
	f[i][j][x][sum]+=f[i-1][j][x-1][sum-a[i]]*(3-x);//让ai做端点并且放在一段的左右
}

这样搞一搞,复杂度O(n3L),不足以通过本题,L应该仍不掉了,考虑扔掉一个n

插入ai的时候,注意到aiai1的贡献是和插入前的段数和端点数量相关的。

j段,x个端点的时候,贡献为(a[i]a[i1])(2jx)

由于我沿用ij寻找i1的转移而来,所以代码写得很蠢。后来又写了一版从ii+1转移的:

    for(int i=0;i<n;i++)
        for(int j=(i!=0);j<=i;j++)
            for(int x=0;x<=2;x++)
                for(int sum=0,tsum=(a[i+1]-a[i])*(2*j-x);tsum<=l;sum++,tsum++)
                {
                    // int tsum=sum+(a[i+1]-a[i])*(2*j-x);
                    f[i+1][j+1][x][tsum]=(f[i+1][j+1][x][tsum]+f[i][j][x][sum]*(j+1-x))%mod;//新开一个
                    f[i+1][j][x][tsum]=(f[i+1][j][x][tsum]+f[i][j][x][sum]*(2*j-x))%mod;//放在某一段的左右
                    if(j)
                        f[i+1][j-1][x][tsum]=(f[i+1][j-1][x][tsum]+f[i][j][x][sum]*(j-1))%mod;//合并两个
                    if(x!=2)
                    {
                        f[i+1][j+1][x+1][tsum]=(f[i+1][j+1][x+1][tsum]+f[i][j][x][sum]*(2-x))%mod;//让ai新开一段
                        f[i+1][j][x+1][tsum]=(f[i+1][j][x+1][tsum]+f[i][j][x][sum]*(2-x))%mod;//让ai做端点
                    }
                }

T6 P2612 波浪

一眼望去,和T5类似,只不过把a[i]=read()变为a[i]=i

仔细一看,0M2147483647,貌似很唬人,但是仔细思考,可以发现循环到n*n/2即可,最大值不会比它更大。

int a[100],n,ans;
void dfs(int d){
	if(d==n+1){
		int sum=0;
		for(int i=2;i<=n;i++)sum=sum+abs(a[i]-a[i-1]);
		ans=max(ans,sum);
		return ;
	}
	for(int i=d;i<=n;i++){
		swap(a[i],a[d]);
		dfs(d+1);
		swap(a[i],a[d]);
	}
}
int main(){
	for(int i=1;i<=20;i++)a[i]=i;
	for(n=1;n<=12;n++){
		dfs(1);
		cout<<n*n/2<<' '<<ans<<'\n';
	}
}

由于要输出小数,不给模数,考虑使用__float128,并且使用函数输出。

void Print(__float128 ans){
    int num[100];
    num[0]=0;
    ans=ans*10;
    for(int i=1;i<K;i++){
        num[i]=(int)ans;
        ans=(ans-num[i])*10;
    }
    num[K]=(int)(ans+0.5);
    for(int i=K;i>=1;i--)if(num[i]>=10)num[i]-=10,num[i-1]++;
    printf("%d.",num[0]);
    for(int i=1;i<=K;i++)printf("%d",num[i]);
    puts("");
}
// 原文链接:https://blog.csdn.net/qq_35320178/article/details/89446547

然后需要滚动数组和卡常。我用了两个技巧,一个是if(!f[now][j][x][sum])continue;,一个是并不边算边除以i,而是最后再一个循环除一下n!

T7 CF1515E Phoenix and Computers

我们继续,f[i][j]表示有i个电脑,组成了j个连续已开启的段。
这里的ij和T1类似,并非指最终开关顺序是1~n顺序地开,而是仅当一个占位符。

例如o是已开,xooxxoox这种方案是f[4][2]之一。

我们枚举i号电脑是怎么开开的:

如果i号电脑是紧挨某一段的:

f[i][j]+=f[i1][j]2j

如果i号电脑是被动开开的,例如1 3 2

f[i][j]+=f[i2][j]2j

如果最后一步是被动开的,并且合并了两个连续段

f[i][j]+=f[i2][j+1]2j

f[i][j]+=f[i3][j+1]j

如果是新的连续段插入:

f[i][j]+=f[i1][j1]j

T8 P7967 Magneti

显然,可以给r排个序,然后处理大r的时候只需要管大r的限制,不需要管小r的限制

大胆dp,优化是以后的事。

f[i][j][sum]表示用了前i个,j段,j段长度之和为sum的方案数。

if(sum>=r[i])f[i][j][sum]+=f[i-1][j][sum-r[i]]*j*2;
//放在某一段的边上
if(sum>=1)f[i][j][sum]+=f[i-1][j-1][sum-1]*j;
//新开一段
if(sum>=2*r[i])f[i][j][sum]+=f[i-1][j+1][sum-2*r[i]]*j;
//合并两段

最后还需要组合数一下。对于f[n][1][i],我们有li个空格给n+1个位置分配,挡板法,ans=f[n][1][i]C(n+li,li)

T9 弹弹床 无数据

题意简述:给定序列 ai0,1。求长为n 的排列的数量,要求对于所有 i[1,n1],如果 api=1 那么pi>pi+1,否则pi<pi+1,对每个kpn=k 的方案数,n5000

posted @   zzuqy  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2019-01-17 BST 万事开头难
点击右上角即可分享
微信分享提示