首页 > 其他分享 >插入类dp

插入类dp

时间:2024-01-17 17:22:06浏览次数:26  
标签:10 le int sum 插入 新开 一段 dp

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

T1 AT_dp_t Permutation

有一个长为 \(N\) 的正整数排列。给定一个由 <> 组成长为 \(N-1\) 的的字符串。
对于任意满足 \(1 \le i \le N-1\) 的字符 \(s_i\),如果 \(s_i\) 是 < 则 \(P_i<P_{i+1}\)、如果 \(s_i\) 是 > 则 \(P_i>P_{i+1}\)。求满足这样的性质的排列 \(P\) 的方案数。

\(N\leq 3000\)

考虑动态规划

\(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]=\sum_{k=1}^{j-1} f[i-1][k]\)

else

\(f[i][j]=\sum_{k=j}^{k=i-1}f[i-1][k]\)

前缀和优化之。

T2 AT_abc209_f Deforestation

\(n\) 个数:\(a_1,a_2,...,a_n\)。

每次可以选择一个 \(i\),选择的代价是 \(a_{i-1}+a_i+a_{i+1}\),然后令 \(a_i=0\)。

求有多少种方案,使得 \(a_1,a_2,...,a_n\) 都变为 \(0\) 的总代价最小。特别的,\(a_0=a_{n+1}=0\)

\(1\leq n\leq 4000,1\leq a_i\leq 10^9\)

考虑这个限制是什么。

对于\(i\)和\(i+1\)

先拿\(i\)再拿\(i+1\),收益是\(a[i]+2*a[i+1]\)

先拿\(i+1\)再拿\(i\),收益是\(a[i+1]+2*a[i]\)

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

于是和T1一样了

\(if(a[i-1]>a[i])\)

\(f[i][j]=\sum_{k=1}^{j-1} f[i-1][k]\)

\(else\ if(a[i-1]<a[i])\)

\(f[i][j]=\sum_{k=j}^{k=i-1}f[i-1][k]\)

\(else\)

\(f[i][j]=\sum_{k=1}^{i-1}f[i-1][k]\)

按段数进行的插入类dp

T3 P5999 kangaroo

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

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

  • 那么如果 \(prev<now\),就必须有 \(next<now\);

  • 如果 \(now<prev\),就必须有 \(now<next\)。

问从 \(s\) 到 \(t\) 的方案数模 \(10^9+7\)的结果。

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

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

\(2\le n\le 2\times 10^3\),\(1\le s,t\le n\)

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

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

\(f[i][j]=f[i-1][j+1]*j+f[i-1][j-1]*j\)

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

s和t的关系如何解决?

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

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

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

当\(i!=s\&\&i!=t\)时

\(f[i][j]=f[i-1][j+1]*j+f[i-1][j-1]*(j-(i>s)-(i>t))\)

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

最后输出\(f[n][1]\)。

T4 CF704B Ant Man

  • 有 \(n\) 个元素,第 \(i\) 个元素有五个参数 \(x_i,a_i,b_i,c_i,d_i\)。
  • 你需要求出一个 \(1 \sim n\) 的排列 \(p\),满足 \(p_1 = s, p_n = e\),同时最小化这个排列的权值。
  • 一个排列的权值为 \(\sum_{i=1}^{n-1} f(p_i, p_{i+1})\),其中 \(f(i,j)\) 的值有两种情况:
    • 若 \(i > j\),则 \(f(i,j) = x_i - x_j + c_i + b_j\)。
    • 若 \(i < j\),则 \(f(i,j) = x_j - x_i + d_i + a_j\)。
  • \(n \le 5 \times 10^3\),\(s \ne e\),\(1 \le x_1 < x_2 < \cdots < x_n \le 10^9\),\(1 \le a_i,b_i,c_i,d_i \le 10^9\)。

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

\(f[0][1]=0\)

普通的情况

\(f[i][j]=f[i-1][j-1]-x[i]*2+b[i]+d[i]\) 新开一段。

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

\(f[i][j]=f[i-1][j+1]+x[i]*2+a[i]+c[i]\)合并两个段。

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

\(f[i][j]=f[i-1][j]+b[i]+c[i]\)往某个段前面放。

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

\(f[i][j]=f[i-1][j]+a[i]+d[i]\)往某个段后面放。

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

对于起点

\(f[i][j]=f[i-1][j]+x[i]+c[i]\)

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

\(f[i][j]=f[i-1][j-1]-x[i]+d[i]\)

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

对于终点

\(f[i][j]=f[i-1][j]+x[i]+a[i]\)

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

\(f[i][j]=f[i-1][j-1]-x[i]+b[i]\)

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

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

影响就是

想新开一段时,不能\(i>s\&\&i>t\&\&j-1==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(n^3L)\),不足以通过本题,\(L\)应该仍不掉了,考虑扔掉一个\(n\)。

插入\(a_i\)的时候,注意到\(a_i-a_{i-1}\)的贡献是和插入前的段数和端点数量相关的。

有\(j\)段,\(x\)个端点的时候,贡献为\((a[i]-a[i-1])*(2*j-x)\)。

由于我沿用\(ij\)寻找\(i-1\)的转移而来,所以代码写得很蠢。后来又写了一版从\(i\)向\(i+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\)。

仔细一看,\(0\leq M\leq 2147483647\),貌似很唬人,但是仔细思考,可以发现循环到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[i-1][j]*2*j\)

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

\(f[i][j]+=f[i-2][j]*2*j\)

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

\(f[i][j]+=f[i-2][j+1]*2*j\)

\(f[i][j]+=f[i-3][j+1]*j\)

如果是新的连续段插入:

\(f[i][j]+=f[i-1][j-1]*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]\),我们有\(l-i\)个空格给n+1个位置分配,挡板法,\(ans=\sum f[n][1][i]*C(n+l-i,l-i)\)。

T9 弹弹床 无数据

题意简述:给定序列 \({a_i}\in{0,1}\)。求长为\(n\) 的排列的数量,要求对于所有 \(i\in[1,n−1]\),如果 \(a_{p_i}=1\) 那么\(p_i>p_{i+1}\),否则\(p_i<p_{i+1}\),对每个\(k\) 求\(p_n=k\) 的方案数,\(n\leq 5000\)。

标签:10,le,int,sum,插入,新开,一段,dp
From: https://www.cnblogs.com/qywyt/p/17970536

相关文章

  • dpkg/ error processing package install-info (--configure)/ installed install-inf
    背景介绍在ubuntu20.04中使用apt安装软件时会出现报错dpkg/errorprocessingpackageinstall-info(--configure)/installedinstall-infopackagepost-installationscriptsubprocessreturnederrorexitstatus126这主要是由于不完全安装导致的。解决方式是删除或编辑......
  • 【树上DP前导知识汇总】
    一、树的直径记录最长、次长,输出\(max(最长+次长)\)\(AcWing\)\(1072\)树的最长路径#include<bits/stdc++.h>usingnamespacestd;constintN=10010,M=N<<1;intn;//n个结点//链式前向星inth[N],e[M],w[M],ne[M],idx;voidadd(inta,intb,intc......
  • E2. Minibuses on Venus (medium version)(卷积加速dp)
    数的范围是在k进制下的n位数一个数是lucky的当且仅当在k进制下,存在一个数位上的数,等于其他数位上的数在模k意义下的和。利用减法原理假设一个数的数位和为s,如果存在一个数,那么有s-x%k=x%k->s%k=2x%k那么我们找到这样的x,就是说在计算和为s的方案数是不能使用这些x类似于dp......
  • m基于码率兼容打孔LDPC码ms最小和译码算法的LDPC编译码matlab误码率仿真
    1.算法仿真效果matlab2022a仿真结果如下:2.算法涉及理论知识概要码率兼容打孔LDPC码BP译码算法是一种改进的LDPC译码算法,能够在不同码率下实现更好的译码性能。该算法通过在LDPC码中引入打孔操作,使得码率可以灵活地调整,同时利用BP(BeliefPropagation)译码算法进行迭代译码,提高了......
  • m基于码率兼容打孔LDPC码ms最小和译码算法的LDPC编译码matlab误码率仿真
    1.算法仿真效果matlab2022a仿真结果如下:    2.算法涉及理论知识概要       码率兼容打孔LDPC码BP译码算法是一种改进的LDPC译码算法,能够在不同码率下实现更好的译码性能。该算法通过在LDPC码中引入打孔操作,使得码率可以灵活地调整,同时利用BP(BeliefPropagation......
  • 网络编程之基于UDP协议的socket套接字编程
    基于UDP的套接字udp是无链接的,先启动哪一端都不会报错【1】方法简介(1)UDP服务端server=socket()#创建一个服务器的套接字server.bind()#绑定服务器套接字inf_loop:#服务器无限循环conn=server.recvfrom()/conn.sendto()#对话(接收与发送)serv......
  • 网络编程TCP UDP
    网络编程(1)什么是网络编程网络编程是指通过编程语言在计算机之间建立通信的一种方式。它是在互联网上进行数据传输的关键组成部分,使计算机能够相互通信、交换信息和共享资源。网络编程涉及许多不同的技术和协议,包括TCP/IP(传输控制协议/因特网协议),HTTP(超文本传输协议),FTP(文件传......
  • 「数位dp」统计整数数目(力扣第2719题)
    本题为1月16日力扣每日一题题目来源:力扣第2719题题目tag:数位dp动态规划题面题目描述给你两个数字字符串num1和num2,以及两个整数max_sum和min_sum。如果一个整数x满足以下条件,我们称它是一个好整数:\(num1\leqx\leqnum2\)\(min\_sum\leqdigit\_sum(x)\leqmax\_s......
  • Scoket层(TCP,TDP)
    【一】Scoket层在哪还是用图来说话,一目了然。【二】什么是socketSocket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面对用户来说,一组简单的接口就是全部,让Socket去组织......
  • C#DataGridView数据批量插入数据库中(测试未果)
    datagridview表格的数据要导入后台数据库表中时,如果记录比较多,用SQL速度慢,尝试用批量导入,未能成功,继续努力;usingNpgsql;usingNpgsqlTypes;usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingS......