首页 > 其他分享 >线头 DP 问题

线头 DP 问题

时间:2024-05-16 19:20:28浏览次数:19  
标签:int 线头 long 问题 DP 转移 dp Mod

引入

对于一种需要通过相邻两项来维护的一些 DP 问题,通常的 DP 会无法转移。这时便要使用线头 DP。这种 DP 又名连续段 DP,其关键在于维护已近满足条件的不同连续段的贡献总和

线头 DP 本质上只是一种转移状态,这种状态能与排列成双射的同时,还能只考虑两端的性质来使状态便于记录。它可以最大程度利用两端插入时的良好性质,同时还能与排列集成双射,快速解决这类依赖邻项的问题。

通常,我们设 \(DP_{i, j}\) 表示考虑了前 \(i\) 个元素,形成了 \(j\) 个满足条件的段落。以下将该段落称为线。我们通过这些线通过新建,合并,延续的方法进行转移。

  • \(DP_{i, j}\) 可以通过 \(DP_{i - 1, j - 1}\) 转移。这表示新建了一个连续段。
  • \(DP_{i, j}\) 可以通过 \(DP_{i - 1, j}\) 转移。这表示延续了一个连续段。
  • \(DP_{i, j}\) 可以通过 \(DP_{i - 1, j + 1}\) 转移。这表示合并了一个连续段。

kangaroo

先考虑没有 \(s, t\) 怎么做。一个数必定会同时大于或小于相邻的数。尝试用上面的方法转移。一个线表现为所有数都大于或小于相邻的数

设 \(DP_{i, j}\) 表示考虑了前 \(i\) 个元素,形成了 \(j\) 个线。我们从小到大加入元素。那么有以下转移。

  • \(DP_{i, j} = DP_{i - 1, j - 1} \times j\)。这表示加入了一个新段。原先有 \(j - 1\) 个线,因此可以插在 \(j\) 个空中。注意这里也考虑了延续的情况
  • \(DP_{i, j} = DP_{i - 1, j + 1} \times j\)。这表示合并。因为我们的 \(j\) 元素比前面的大,所以保证可以。

若有 \(s, t\),我们发现:

  • 若 \(i = s \text{或} t\),有 \(DP_{i, j} = DP_{i - 1, j} + DP_{i - 1, j - 1}\)。这表明合并或新加。
  • 若 \(i\) 已过 \(s\) 则不能加头,若 \(i\) 已过 \(t\) 则不能插尾。
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 3e3, Mod = 1e9 + 7;
int n, l, r;
int dp[N][N];

signed main(){
	cin >> n >> l >> r;
	dp[1][1] = 1;
	for(int i = 2; i <= n; i++)
		for(int j = 1; j <= n; j++){
			if(i == l || i == r)
				dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - 1]) % Mod;
			else{
				dp[i][j] = dp[i - 1][j - 1] * j % Mod;
				if(l < i) dp[i][j] = (dp[i][j] - dp[i - 1][j - 1] + Mod) % Mod;
				if(r < i) dp[i][j] = (dp[i][j] - dp[i - 1][j - 1] + Mod) % Mod;
				dp[i][j] = (dp[i][j] + dp[i - 1][j + 1] * j % Mod) % Mod;
			}
		}
	cout << dp[n][1] << endl;
	return 0;
}

高層ビル街

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 105, Mod = 1e9 + 7;
int n, l, a[N], dp[2][N][1005][2][2];

bool cmp(int x, int y){return x > y;}
void add(int& u, int v){
	u %= Mod, v %= Mod;
	u = (u + v + Mod) % Mod;
	return ;
}

signed main(){
	cin >> n >> l;
	for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + 1 + n, cmp);
	
	// dp[i & 1][cnt][sum][left][right] 表示当前考虑到 a[i], 
	// 有 cnt 个连续段,和为 sum,左边和右边是否固定的方案数
	// 初始的时候,只加了 1 个数,sum 此时是零 
	dp[1][1][0][0][0] = 1; dp[1][1][0][0][1] = 1;
	dp[1][1][0][1][0] = 1; dp[1][1][0][1][1] = 1;
	for(int i = 2; i <= n; i++){
		bool u = i & 1, v = !u; // 当前的状态,之前的状态
		// 清空当前的状态 
		for(int cnt = 1; cnt <= i; cnt++) for(int sum = 0; sum <= l; sum++)
			dp[u][cnt][sum][0][0] = 0, dp[u][cnt][sum][0][1] = 0, 
			dp[u][cnt][sum][1][0] = 0, dp[u][cnt][sum][1][1] = 0;
		for(int cnt = 1; cnt < i; cnt++) for(int sum = 0; sum <= l; sum++)
			for(int L = 0; L <= 1; L++)
				for(int R = 0; R <= 1; R++){
					// 每一个段落会贡献两个高度,但如果以确定左右就会少贡献
					int newSum = sum + (a[i - 1] - a[i]) * (2 * cnt);
					if(L) newSum -= (a[i - 1] - a[i]);
					if(R) newSum -= (a[i - 1] - a[i]);
					if(l < newSum) continue;
					// 在之前的块里面加一个新块,块数要大于 1,总共有 cnt - 1 种填法 
					// 或合并块,也是 cnt - 1 种 
					if(cnt != 1) add(dp[u][cnt + 1][newSum][L][R], (cnt - 1) * dp[v][cnt][sum][L][R]);
					if(cnt != 1) add(dp[u][cnt - 1][newSum][L][R], (cnt - 1) * dp[v][cnt][sum][L][R]);
					// 在左右边新建块,要注意 L/R = 0 
					if(!L) add(dp[u][cnt + 1][newSum][0][R], dp[v][cnt][sum][0][R]), 
						   add(dp[u][cnt + 1][newSum][1][R], dp[v][cnt][sum][0][R]);
					if(!R) add(dp[u][cnt + 1][newSum][L][0], dp[v][cnt][sum][L][0]), 
						   add(dp[u][cnt + 1][newSum][L][1], dp[v][cnt][sum][L][0]);
					// 在之前的块里面加进一个旧块,块数要大于 1,总共有 2 * cnt - 2 种填法 
					if(cnt != 1) add(dp[u][cnt][newSum][L][R], 2 * (cnt - 1) * dp[v][cnt][sum][L][R]);
					//	在左右边加旧块,要注意 L/R = 0 
					if(!L) add(dp[u][cnt][newSum][0][R], dp[v][cnt][sum][0][R]), 
						   add(dp[u][cnt][newSum][1][R], dp[v][cnt][sum][0][R]);
					if(!R) add(dp[u][cnt][newSum][L][0], dp[v][cnt][sum][L][0]), 
						   add(dp[u][cnt][newSum][L][1], dp[v][cnt][sum][L][0]);
				}
	}
	// 统计答案 
	int ans = 0;
	for(int i = 0; i <= l; i++) add(ans, dp[n & 1][1][i][true][true]);
	cout << ans << endl;
	return 0;
}

标签:int,线头,long,问题,DP,转移,dp,Mod
From: https://www.cnblogs.com/lc0802/p/18196553

相关文章

  • 群晖ds1517+解决第三方Marvell AQC107 10Gbe网卡驱动问题
    群晖ds1517+解决第三方MarvellAQC10710Gbe网卡驱动问题转载注明来源:本文链接来自osnosn的博客,写于2024-05-15.说明这是网友mohawk解决问题的经过,征得同意后,贴在这里。给大家参考。背景好友打算升级到全屋有线万兆2.5g网络,陆续装备了路由器、交换机等,但家里的群晖ds......
  • docker 安装问题
    前提linux版本linuxmintjammy使用的是aliyun的apt源问题sudoaptupgrade后总是提示下列软件包有未满足的依赖关系:docker-ce:依赖:containerd.io(>=1.2.2-3)但是它将不会被安装依赖:libseccomp2(>=2.3.0)但是2.2.3-3ubuntu3正要被安装推荐:aufs-tools......
  • MySql5.6 关于视图访问权限问题记录
    问题描述使用zstack或root账号访问视图view3出现[root@172-26-52-170mariadb]#mysql-uzstack-pzstack.passwordzstack-e"select*fromview3"ERROR1045(28000)atline1:Accessdeniedforuser'zstack'@'localhost'(usingpassword:YES)......
  • 实战项目-基于K8s平台进行wordpress建站
    (240516更新)基本信息系统:Debian12.05k8s版本:1.30环境:虚拟机序号IP地址域名主机名1192.168.100.12k8s-master.$yourname.comk8s-master2192.168.100.15k8s-node1.yourname.comk8s-node13192.168.100.16k8s-node2.yourname.comk8s-node24192.168......
  • python打包在32位无法运行问题
    真不想吐槽现在的技术越高级越烂的一批尤其是开发工具win1064位python64位开发pyinsataller打包后不能在32位上运行别折腾重新安装python32位测试安装python3.12.232位竟然不能安装pandas(见鬼去吧)重新安装python3.8.10提示不能用在xp上,也可以接受了.再安装依赖包,没......
  • 无法AC,关于使用fgets碰到的问题——末尾多一个换行符
    题目是输入一串字符串,包含空格,里面有多个单词,将每个单词翻转输出,并且单词之间的空格要与原文一致。写的时候没有使用string的输入,而是选择了char数组的输入。样例测试helloworld->ollehdlrow是没有问题的,就以为没问题,但是一直通不过。调试的时候,变量也是有些神奇,不过这个是系......
  • python算法:打字员问题
    一,题目:现有一堆稿件,甲单独打字完成需要6小时,乙单独打字完成需要10小时,甲工作了若干小时后因家中有事由乙接着干,两人完成稿件一共用了7小时,问甲打字用了几个小时?二,解析:1,为了方便计算,我们假设这堆稿件分成60份,可以得到:甲每小时打10份,乙每小时打6份,设甲用时x,取值范围:[......
  • SDP协议
    会话描述协议,一般用国标SIP交互媒体信息(offer和应答),RTSP中describe协商媒体信息(只有应答的SDP,没有offer_sdp),webrtc协议交互阶段(offer和answer)时候回存在;本例只介绍SIP-SDP,对于荷载它的协议不做描述原文:https://sharetechnote.com/html/IMS_SIP_SDP.html1国标交互的时候为了让......
  • python算法:握手问题
    一,题目小明在家中举办派对,请邀请好友来参加,来参加宴会的每两个人之间要握手,而且是仅握手一次,则当人数为n时总共需要握手多少次?二,解析1,思路:我们假设每个人到达后按先后顺序握手:这样从人数最少时开始分析:开始时会场中只有小明,是参会的第一个人,假设第二个人到达时,与小明握......
  • 期望DP
    基本模型对于任意状态A,已知①状态A所有后继状态②设从状态A转移到后继状态B的概率是P(A,B),则∑P(A,B)=1③从状态A转移到状态B的花费是W(A,B)求解:从起始状态S到终止状态T的期望花费求解的基本模式设E(A)表示从状态A到终止状态T的期望花费,初值:E(T)=0......