[SOCI2005]最大子矩阵(DP) + [JXOI2018]守卫(DP) + [CQOI2016]手机号码(数位DP)[各种DP专练]

DP专练博客

DP专练

  • T1:最大子矩阵
    • 题目
    • 题解
    • 代码实现
  • T2:守卫
    • 题目
    • 题解
    • 代码实现
  • T3:手机号码
    • 题目
    • 题解
    • 代码实现

在这里插入图片描述

T1:最大子矩阵

题目

这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的k个子矩阵不能相互重叠。

输入格式
第一行为n,m,k(1≤n≤100,1≤m≤2,1≤k≤10),接下来n行描述矩阵每行中的每个元素的分值(每个元素的分值的绝对值不超过32767)。
输出格式
只有一行为k个子矩阵分值之和最大为多少。

输入输出样例
输入
3 2 2
1 -3
2 3
-2 3
输出
9

题解

首先看到这个小的数据范围(尤其是m),就不明所以然地心动。。。

我们先来考虑m=1的情况
这个问题就转化为了求一个区间的k个互不重复的线段的和的最大值
肯定都会做DP[i][j]DP[i][j]DP[i][j]:表示到i位置为止一共已经选了j个区间的合法最大值
状态转移方程如下:
DP[i][j]=max(DP[last][j−1]+val[i],DP[last][j])(0≤last<i)DP[i][j]=max(DP[last][j-1]+val[i],DP[last][j])(0≤last<i)DP[i][j]=max(DP[last][j1]+val[i],DP[last][j])(0last<i)


那么受m=1的影响启发,虽然矩阵变成了二维,也不会变得过于复杂
DP[i][j][k]DP[i][j][k]DP[i][j][k]:表示第一列已经枚举到了i位,第二列已经枚举到了j位,一共选了k个矩阵
但是可以发现当i==ji==ji==j的时候这个状态可以由i和j同时以宽度为2进行转移,所以会多一个转移方程式
综上,转移方程式如下:
DP[i][j][k]={max(DP[lasti][j][k−1]+val[i][j],DP[i][lastj][k−1]+a[i][j])val就是从last到i为止这个宽度为2的子矩阵值的和:DP[i][j][k]=max(DP[last][last][k−1]+val)(i==j)别忘了还可以不选这个点:DP[i][j][k]=max(DP[last][j][k],DP[i][last][k])DP[i][j][k]= \left\{ \begin{array}{rcl} max(DP[lasti][j][k-1]+val[i][j],DP[i][lastj][k-1]+a[i][j])\\ val就是从last到i为止这个宽度为2的子矩阵值的和:\\ DP[i][j][k]=max(DP[last][last][k-1]+val)(i==j)\\ 别忘了还可以不选这个点:\\ DP[i][j][k]=max(DP[last][j][k],DP[i][last][k])\end{array} \right. DP[i][j][k]=max(DP[lasti][j][k1]+val[i][j],DP[i][lastj][k1]+a[i][j])vallasti2:DP[i][j][k]=max(DP[last][last][k1]+val)(i==j):DP[i][j][k]=max(DP[last][j][k],DP[i][last][k])


最后分享如何计算这个子矩阵的值
我们可以定义一个二维数组sum[i][3]第二维表示是第几列,i表示本列到第i位时的值
这样val就变得很好算了

代码实现

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n, m, k;
int matrix[105][3], sum[105][3];
int dp[105][105][15];
int main() {scanf ( "%d %d %d", &n, &m, &k );memset ( dp, -0x7f, sizeof ( dp ) );for ( int i = 1;i <= n;i ++ )for ( int j = 1;j <= m;j ++ ) {scanf ( "%d", &matrix[i][j] );sum[i][j] = sum[i - 1][j] + matrix[i][j];}for ( int i = 0;i <= n;i ++ )for ( int j = 0;j <= m;j ++ )dp[i][j][0] = 0;for ( int p = 1;p <= k;p ++ )for ( int i = 1;i <= n;i ++ )for ( int j = 1;j <= n;j ++ ) {dp[i][j][p] = max ( dp[i - 1][j][p], dp[i][j - 1][p] );for ( int last = 0;last < i;last ++ )dp[i][j][p] = max ( dp[i][j][p], dp[last][j][p - 1] + sum[i][1] - sum[last][1] );for ( int last = 0;last < j;last ++ )dp[i][j][p] = max ( dp[i][j][p], dp[i][last][p - 1] + sum[j][2] - sum[last][2] );if ( i == j )for ( int last = 0;last < i;last ++ )dp[i][j][p] = max ( dp[i][j][p], dp[last][last][p - 1] + sum[i][1] - sum[last][1] + sum[j][2] - sum[last][2] );}	printf ( "%d", dp[n][n][k] );	return 0;
} 

没有放表情包真是让人怪不舒服的,ヾ(•ω•`)o
在这里插入图片描述

T2:守卫

题目

九条可怜是一个热爱运动的女孩子

这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。

具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有 n 个折点,每个折点上有一个亭子,第 ii 个折点的坐标是(i,hi) 。九条可怜只可能会在亭子处玩耍,那些保镖也只会在亭子处监视可怜。

由于技术方面的原因,一个保镖只能监视所有他能看得到的,横坐标不超过他所在位置的亭子。我们称一个保镖能看到一个亭子 p ,当且仅当他所在的亭子 q 和 p 的连线不经过任何一块岩石。特别地,如果这条连线恰好经过了除了 p,q 以外的亭子,那么我们认为保镖看不到可怜。

雇佣保镖是一件很费钱的事情,可怜的父亲希望保镖越少越好。

可怜的父亲还希望得到详尽的雇佣保镖的方案,他知道有些亭子可能正在维修,他想对所有的 1≤l≤r≤n 计算:如果事先已知了只有区间 [l,r] 的亭子可以用来玩耍(和监视),那么最少需要多少个保镖,才能让[l,r] 中的每一个亭子都被监视到。

可怜的父亲已经得到了一个结果,他希望和你核实他的结果是否正确。

输入格式
第一行输入一个整数n表示亭子的数目。 接下来一行n个整数,第 i 个整数 hi表示第i个亭子的坐标是(i,hi)
输出格式
对所有的1≤l≤r≤n 计算:如果事先已知了可怜只会在 [l,r] 这个区间的亭子里面玩耍,那么最少需要多少个保镖,才能让 [l,r] 中的每一个亭子都被监视到。由于输出量太大,可怜的父亲只要你输出所有[l,r]中的每一个亭子都被监视到。由于输出量太大,可怜的父亲只要你输出所有[l,r]的答案的异或即可。

输入输出样例
输入
3
2 3 1
输出
3
说明/提示
样例解释
如果r−l+1≤2 ,那么答案显然是1 。 如果l=1,r=n ,那么答案是 2 ,需要安排两个保镖在 (2,3),(3,1) 两个位置监视可怜。

数据范围与提示
对于 30% 的数据,n≤20 。
对于 70% 的数据,n≤500 。
对于 100% 的数据,n≤5000,1≤hi≤109

题解

首先通过题意我们知道对于[l,r][l,r][l,r]而言,守卫只能安排在这个区间且只能往前看,他的脖子不允许他转180
那么r这个点就必须安排一个守卫


在这里插入图片描述
观察这个图,假设现在守卫在r,那么他能看见的亭子必须夹角得逐步变大
也就是说他的视线在不断往上移,是无法往下看的,这个可以利用两点确定一条直线的K值进行判断
我们可以O(n2)O(n^2)O(n2)预处理出i,j两个点之间是否可以彼此看见
在这里插入图片描述
紧接着不难想到,r看不见的区间是彼此相互独立的,即
r可以监视的一个点x,那么[x+1,r)的点都不能监视比x更靠前的点
感性理解:x太高了,挡住了后面点的视线,使得他们看不见x前面的点
举个栗子理解:就是在第二个绿色板块里面的点无法看见第一个绿色板块里面的点,
因为第二根黄线的那个点太高了,遮住了第二个绿色板块点的视线


DP[l][r]DP[l][r]DP[l][r]表示在[l,r][l,r][l,r]区间中最少要设多少个守卫

知道了当我们固定右端点,那么这个点不能监视到的一些区间[li,ri][li,ri][li,ri]是相互独立的,
所以想要监视[li,ri][li,ri][li,ri]就必须在ririri或者ri+1ri+1ri+1处设置保镖,在这里插入图片描述
于是DP转移方程式
dp[l][r]=1+∑min(dp[li][ri],dp[li][ri+1])dp[l][r]=1+∑min(dp[li][ri],dp[li][ri+1])dp[l][r]=1+min(dp[li][ri],dp[li][ri+1])

这样做是O(n3)的,但是发现对于每一个[li,ri][li,ri][li,ri]区间只与紧邻的后一个看不见的区间的有瓜葛
我们就可以考虑枚举右端点,这样就可以在往前面扫的过程中维护那个∑∑,从而复杂度降到了O(n2)

代码实现

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define MAXN 5005
int n, result;
int h[MAXN];
int dp[MAXN][MAXN];
bool look[MAXN][MAXN];
int main() {scanf ( "%d", &n );for ( int i = 1;i <= n;i ++ )scanf ( "%d", &h[i] );for ( int i = 1;i <= n;i ++ ) {double k = 1e9;for ( int j = i - 1;j;j -- )if ( ( h[i] - h[j] ) * 1.0 / ( i - j ) < k ) {k = ( h[i] - h[j] ) * 1.0 / ( i - j );look[j][i] = 1;}}dp[1][1] = dp[2][2] = dp[1][2] = 1;for ( int i = 3;i <= n;i ++ ) {int sum = 0, last = i;dp[i][i] = 1;for ( int j = i - 1;j;j -- ) {if ( look[j][i] ) {if ( ! look[j + 1][i] )sum += min ( dp[j + 1][last], dp[j + 1][last - 1] );dp[j][i] = sum + 1;last = j;}elsedp[j][i] = min ( dp[j][last - 1], dp[j][last] ) + sum + 1;}}for ( int i = 1;i <= n;i ++ )for ( int j = 1;j <= i;j ++ )result ^= dp[j][i];printf ( "%d", result );return 0;
}

T3:手机号码

题目

人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。

工具需要检测的号码特征有两个:号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。

手机号码一定是 11 位数,前不含前导的 0。工具接收两个数 L 和 R,自动统计出 [L,R]区间内所有满足条件的号码数量。L 和 R 也是 11 位的手机号码。

输入格式
输入文件内容只有一行,为空格分隔的 2 个正整数 L,R
输出格式
输出文件内容只有一行,为 1 个整数,表示满足条件的手机号数量。

输入输出样例
输入
12121284000 12121285550
输出
5
说明/提示
样例解释:满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550。

数据范围:1010≤L≤R<1011

题解

首先我们可以将[l,r][l,r][l,r]运用差分思想转化成[1,r]−[1,l−1][1,r]-[1,l-1][1,r][1,l1],这肯定大家都想得到


对于这种位数固定的,且比较小的题目,就可以思考数位DP
在这里插入图片描述
DP[p][last][lastDP[p][last][lastDP[p][last][last_last][flag][limit][flaglast][flag][limit][flaglast][flag][limit][flag_4][flag4][flag4][flag_8]8]8]表示:
模拟到电话号码的p位时,p+1位的值为last,p+2位的值为last_last

flag表示到当前状态为止,是否已经出现了至少3 个相邻的相同数字
flag=1:已经出现;flag=0:还未出现

limit表示到当前状态为止,前面是否都取的是最大值
limit=1:抵着最大值取的;limit=0:不是抵着最大值取的
eg:12345678910,当p指向3时,limit=1,则表示p+1位取的是2,p+2位取的是1
都是取的当前位能取的最大值

flag_4表示到当前状态为止,是否出现过数字4
flag_4=1:已经出现;flag_4=0仍未出现

flag_8表示到当前状态为止,是否出现过数字8
flag_8=1:已经出现;flag_8=0仍未出现
在这里插入图片描述


这里要注意几个点:
1.flag_4和flag_8不能同时为1,在搜索时要特判处理
2.我们由于要把电话号码值x拆分成各个数位,我们储存i位的值的num数组是倒着的
所以搜索时就从大到小,这也是为什么上面的例子p+1的电话数位反而在p前面
3.如果搜索到p时,limit是1,那么这个数位的值就被限制了[0,num[p]][0,num[p]][0,num[p]]
否则,这个数位就可以取[0,9][0,9][0,9]
4.可采取记忆化搜索进行剪枝


转移方程式:
p肯定是p-1,p的上一个就变成了p-1的上上一个,p就变成了p-1的上一个

flag如果先前是1,就不改变,如果在p时刚好p与p+1与p+2相等,就可以变成1,
往下传,所以这里我们要用∣∣||

limit如果是1,那么这个位置如果也取的最大值,就往下传;但如果不是,后面的数位还是可以取[0,9][0,9][0,9],所以这里要用&&\&\&&&

flag_4如果之前出现过,就传下去;如果当前位是4,就可以赋值成1,所以用∣∣||
flag_8亦是如此

其实很简单的,只要会数位DP,可是我不会啊,这就很尴尬了 在这里插入图片描述

代码实现

#include <cstdio>
#include <cstring>
#define LL long long
LL dp[15][15][15][2][2][2][2];
int num[15];LL dfs ( int p, int last, int last_last, bool flag, bool limit, bool flag_4, bool flag_8 ) {if ( flag_4 && flag_8 )return 0;if ( p == 0 )//搜索到末尾了return flag;//flag=1即出现过至少三个是连续的相同数字,这一个电话号码就是合理的if ( dp[p][last][last_last][flag][limit][flag_4][flag_8] != -1 )return dp[p][last][last_last][flag][limit][flag_4][flag_8];int Limit = limit ? num[p] : 9;LL ans = 0;for ( int i = 0;i <= Limit;i ++ )ans += dfs ( p - 1, i, last, flag || ( i == last && i == last_last ), limit && ( i == Limit ), flag_4 || ( i == 4 ), flag_8 || ( i == 8 ) );return dp[p][last][last_last][flag][limit][flag_4][flag_8] = ans;
}LL solve ( LL x ) {if ( x < 1e10 )//特判,如果位数不够电话号码的11位return 0;LL result = 0;memset ( dp, -1, sizeof ( dp ) );int len = 0;while ( x ) {num[++ len] = x % 10;x /= 10;}for ( int i = 1;i <= num[len];i ++ )//不能出现前导零result += dfs ( 10, i, 0, 0, ! ( i < num[len] ), i == 4, i == 8 );return result;
}int main() {LL l, r;scanf ( "%lld %lld", &l, &r );printf ( "%lld", solve ( r ) - solve ( l - 1 ) );return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318208.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【做题记录】位运算

CF1592E Bored Bakry 题意&#xff1a;找出最长的区间 \([l,r]\) 满足 \(a_{l}\&a_{l1}\&\dots\&a_{r-1}\&a_{r}>a_{l}\oplus a_{l1}\oplus\dots\oplus a_{r-1}\oplus a_{r}\) 。 首先发现如果有一段满足这个条件的区间&#xff0c;那么一定有一个(较高的)二…

IdentityServer4-EF动态配置Client和对Claims授权(二)

本节介绍Client的ClientCredentials客户端模式&#xff0c;先看下画的草图&#xff1a;一、在Server上添加动态新增Client的API 接口。为了方便测试&#xff0c;在Server服务端中先添加swagger&#xff0c;添加流程可参考&#xff1a;https://www.cnblogs.com/suxinlcq/p/67575…

P3178 [HAOI2015]树上操作

P3178 [HAOI2015]树上操作 题意&#xff1a; 题解&#xff1a; 这已经是很裸的树链剖分了。。。 直接套模板 代码&#xff1a; #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespac…

8.18模拟:构造

文章目录前言收获考场复盘T1T2T3T4总结前言 190分 10006030 明明是dfs专题 不太理想qwq 写了三个dfs就离谱 最不满意的是T2的爆零 其实分类讨论一下是很可做的 而且暴力还因为没开ll挂掉了… 不过毕竟构造题之前几乎没有做过 所以慢慢来吧 收获 一些构造题的trick 调整法数…

P5371-[SNOI2019]纸牌【矩阵乘法】

正题 题目链接:https://www.luogu.com.cn/problem/P5371 题目大意 有nnn种牌&#xff0c;每种牌最多CCC张&#xff0c;XXX个限制形如kik_iki​种牌至少aia_iai​张。 求所有牌的序号能分成(i,i,i)(i,i,i)(i,i,i)或者(i,i1,i2)(i,i1,i2)(i,i1,i2)的若干组的方案数。 1≤n≤10…

dotnet core开源博客系统XBlog介绍

XBlog是dotnet core平台下的个人博客开源系统&#xff0c;它只需要通过Copy的方式即可以部署到Linux和windows系统中&#xff1b;如果你有安全证书那只需要简单配置一下即可提供安全的Https服务。接下来主要介绍XBlog功能、部署和基础设置。技术要点基于dotnet core平台&#x…

【做题记录】max-min+1=len 区间计数

(来源&#xff1a;XJ高质量原创题) 原题地址 弱化版&#xff1a;CF526F Pudding Monsters 弱化版 题意&#xff1a;\(n\times n\) 的棋盘上有 \(n\) 颗棋子&#xff0c;每行每列都有且仅有一颗棋子&#xff0c;求出有多少个 \(k\times k\) 的子棋盘也满足每行每列只有一颗棋子。…

【莫队/树上莫队/回滚莫队】原理详解及例题:小B的询问(普通莫队),Count on a tree II(树上莫队),kangaroos(回滚莫队)

文章目录问题引入介绍莫队算法及其实现过程时间复杂度莫队算法适用范围莫队奇偶优化普通莫队&#xff1a;小B的询问树上莫队&#xff1a;SP10707 COT2 - Count on a tree II回滚莫队&#xff1a;[PA2011]Kangaroosupd&#xff1a;2021-08-11&#xff1a;重新对博客进行了外观美…

P2146 [NOI2015] 软件包管理器

P2146 [NOI2015] 软件包管理器 题意&#xff1a; 如果软件包 a 依赖软件包 b&#xff0c;那么安装软件包 a 以前&#xff0c;必须先安装软件包 b。同时&#xff0c;如果想要卸载软件包 b&#xff0c;则必须卸载软件包 a。 软件包之间存在依赖关系&#xff0c;除了0号软件包以…

微软 2018 开源大事记

从微软公开宣布 "Microsoft love linux" 那一刻起&#xff0c;过去的几年里&#xff0c;微软积极拥抱开源的举动我们有目共睹&#xff0c;即便有过"Linux is a cancer"这种真香警告的 flag&#xff0c;但不得不承认的是&#xff0c;微软一系列“拥抱开源”…

模板:二叉搜索树平衡树

文章目录前言二叉搜索树代码treap代码splay开点旋转splay插入查找第k大元素查找给定元素的排名前驱&后继删除完整代码练习总结前言 终于开始学这个东西了 看了好几篇博客才找到一篇可读的qwq 我曾经还以为线段树码量大…我真傻&#xff0c;真的 所谓平衡树&#xff0c;就是…

51nod2626-未来常数【树上启发式合并,线段树】

正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId2626 题目大意 给出nnn个点的一棵树&#xff0c;每个区间[l,r][l,r][l,r]的代价是选出这个区间中的一个点xxx使得它走到所有点然后又回到xxx的路程最短长度&#xff0c;求一个随机区间的期望代价。 1≤n…

A*,IDA*—高档次的暴搜

A*通过评价函数来判断当前状态是否可以到达最终状态(即可行性剪枝)&#xff0c;来减少不必要的搜索。 例题——P2324 [SCOI2005]骑士精神 我们通过当前不在指定位置上的棋子个数为评价函数&#xff0c;\(used\) 【评价函数值】超过了预期的值&#xff0c;便不用再线下深入搜索了…

P2486 [SDOI2011]染色

P2486 [SDOI2011]染色 题意&#xff1a; 题解&#xff1a; 与一般的树链剖分相比&#xff0c;不同点在于查询的不是路径上颜色的数量而是颜色段的数量 对于两个颜色段&#xff0c;112和221&#xff0c;两个颜色段数量都是2 如果合在一起颜色段的数量就是3&#xff0c;因为左边…

牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

文章目录T1&#xff1a;仓鼠的石子游戏题目题解代码实现T2&#xff1a;乃爱与城市拥挤程度题目题解代码实现T3&#xff1a;小w的魔术扑克题目题解代码实现T1&#xff1a;仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑&#xff0c;无聊的他们跑到一块空地上&#xff0c;空地上有…

使用PerfView监测.NET程序性能(二):Perfview的使用

在上一篇博客使用PerfView监测.NET程序性能&#xff08;一&#xff09;&#xff1a;Event Trace for Windows 中&#xff0c;我们了解了对Windows及应用程序进行性能分析的基础&#xff1a;Event Trace for Windows (ETW)。现在来看看基于ETW的性能分析工具——Perfview.exePer…

连通性相关

强联通分量 强连通&#xff1a;有向图 \(G\) 强连通表示&#xff0c;\(G\) 中任意两个结点连通。 强连通分量( Strongly Connected Components &#xff0c;简称 \(\operatorname{SCC}\) )&#xff1a;极大的 强连通子图。 Tarjan 维护了以下两个变量&#xff1a; \(dfn\) &…

CF505E-Mr. Kitayuta vs. Bamboos【贪心,二分】

正题 题目链接:https://www.luogu.com.cn/problem/CF505E 题目大意 开始一个长度为nnn的序列hhh&#xff0c;mmm天每天你可以kkk次选择一个hih_ihi​让它等于himax{hi−p,0}h_imax\{h_i-p,0\}hi​max{hi​−p,0}&#xff0c;然后结束时让每个hihiaih_ih_ia_ihi​hi​ai​&…

阶段总结:8.09-8.18 十日模拟

一图流了解一下 文章目录十日谈总结十日谈 再总结一下 8.09 搜索模拟&#xff1a;25分。…毕竟是第一天不太适应吧 &#xff08;拼命找借口&#xff09;。没有看到标题就很淦&#xff0c;就是全写挂了而已&#xff0c;已经无从谈起…hzwer的粉丝那题提醒我们不要被吓人的数据…

学习有向图和无向图的强连通分量(基本概念+割点+点双联通分量+桥+边双连通分量+全套模板【Tarjan】)

最近总是考到Tarjan&#xff0c;让我措手不及基本概念割点以及点双连通分量Tarjan法求割点推导过程代码实现Tarjan法求点双连通分量推导过程代码实现有向图的Tarjan缩点桥与边双连通分量Tarjan法求桥理论推导代码实现Tarjan法求边双连通分量理论推导代码实现前言&#xff1a;有…