蓝桥杯---动态规划(1)
创始人
2024-03-08 08:22:50
0

动态规划专题(1)

  • 1.糖果(状压dp)
  • 2.调手表(状压dp)
  • 3.矩阵计数(状压dp)
  • 4.蒙德里安的梦想(状压dp模板题)
  • 5.跳跃(动态规划,搜索)
  • 5.字符排序(逆序对(难))
  • 6.子串分值和(哈希表+子串小推导)
  • 7.游园安排(最长不上升子序列dp)

1.糖果(状压dp)

方法:状态压缩+简单dp(简称:状压dp)
状压dp方法步骤:

1.预处理好状态
2.使用处理好的状态进行dp

思路:对于每袋糖果,要么选,要么不选。对于当前选择的一袋糖果,如果
这袋糖果里面有之前没有尝过的,那么选择它,如果这袋糖果所有的口味都已经尝过,那么就不要这一袋。那么只需要将当前的尝过的口味的状态表示处理,就可以用简单的01背包去完成这道题。

在这里插入图片描述

#include 
#include
#include
using namespace std;const int N=(1<<20)+10,INF=0x3f3f3f3f;int w[N];  //存放每一袋糖果的状态
int dp[N];   //dp[i]=j表示状态为i的情况下最少尝多少袋糖果int n,m,k;
int main()
{cin>>n>>m>>k;   //袋数,口味种类,每袋的数量memset(dp,-1,sizeof dp);   for(int i=1;i<=n;i++){for(int j=0;jint c;cin>>c;w[i]|=(1<<(c-1)); //运算符‘|’的规则是有1则1。//解释一下这句话.//1<for(int j=0;j<(1<if(dp[j]!=-1)  //如果当前状态存在,那么说明这个可以由这个状态去转移。{if((dp[j|w[i]])==-1) //选择第i袋糖果的状态没有存在,则说明找到了新口味,选它!dp[j|w[i]]=dp[j]+1;else      //如果选了第i袋的状态和没有选i的状态是一样的。说明(j|w[i])这个状态的最小数之前算过了,那么比较一下选这一袋和之前的袋数哪个小dp[j|w[i]]=min(dp[j]+1,dp[j|w[i]]);}else  //否则这个状态一定是第第一次出现{continue;}}}cout<

2.调手表(状压dp)

简单dp+预处理
在这里插入图片描述
在这里插入图片描述

#include
#include
#include
using namespace std;const int N=100100;int dp[N];long long int n,k,j;int main()
{cin>>n>>k;dp[0]=0;for(int i=1;idp[i]=i;if(i%k==0&&i>=k)dp[i]=i/k;}for(int i=1;ij=i*k%n;dp[j]=min(dp[j],i);}for(int i=2;i//要考虑是否走若干1,又调k绕圈到达某分钟步数最小dp[i]=min(dp[i],min(dp[(i+n-k)%n],dp[i-1]))+1;}int res=0;for(int i=1;i

3.矩阵计数(状压dp)

在这里插入图片描述

思路:搜索或者状压dp
状压dp:(1)预处理合法状态。(2)定义dp。 (3)根据dp定义求状态转移方程
这道题的dp定义:dp[i][j][k]表示前i层,第i层的状态是j,第i-1层的状态是j-1的合法方案数

#include 
#include
#include
#include
using namespace std;const int N=1<<6;
vectorstate; //存放合法状态
int dp[N][N][N];   //dp[i][j][k]表示前i层,第i层状态j,第i-1层状态时k的合理方案数量
int res=0;
int n,m;
bool check(int x)  //挑选出合法状态
{//不能有连续3个1int sum=0;  //记录1的个数while(x){if(x&1)  //‘|‘:同1则1{sum++;if(sum>=3)return false;}elsesum=0;x>>=1;}return true;
}int main()
{cin>>n>>m;//预处理状态//0表示0,1表示xfor(int i=0;i<1<if(check(i))state.push_back(i);}memset(dp,0,sizeof dp);for(int i=0;ifor(int j=0;jif((state[j]&state[k]&state[L])==0)  //表示合法状态dp[i][state[j]][state[k]]+=dp[i-1][state[k]][state[L]];}}int ans=0;for(int i=0;i

用搜索来做

#include
#include
#include
#include
using namespace std;const int N=1<<6;
int v[N][N];
int n,m;
int ans=0;bool check()  //检查当前矩阵是否合法
{for(int i=1;i<=n;i++)//考虑到可能每一行不足三个数{for(int j=1;j<=m;j++){if(j+2<=m){if(v[i][j]==1&&v[i][j+1]==1&&v[i][j+2]==1)return false;}if(i+2<=n){if(v[i][j]==1&&v[i+1][j]==1&&v[i+2][j]==1)return false;}}}return true; 
}void dfs(int x,int y)  //一个一个枚举
{if(x==n&&y==m+1){if(check())ans++;return;}if(y==m+1) //说明该行已经枚举完,下一行{y=1;  //从新的一行第一列开始x++;}v[x][y]=0;dfs(x,y+1);v[x][y]=1;dfs(x,y+1);
}int main()
{cin>>n>>m;//用搜索来做//一行一行来枚举dfs(1,1);   //从1行1列开始枚举cout<

4.蒙德里安的梦想(状压dp模板题)

5.跳跃(动态规划,搜索)

在这里插入图片描述
在这里插入图片描述
最基本的dp问题。递推即可。对于任意一格,有9种走法。实际上将题目简化一下就是对于任意一步只有两种走法。但是本质是一样的

#include 
#include
#include
using namespace std;
const int N=110;
int w[N][N];
int dp[N][N];   //dp[i][j]表示当前位于(i,j)的位置上的总价值int n,m;
int main()
{cin>>n>>m;for(int i=0;i>w[i][j];//定义dp,推出dp方程//dp[i][j]=max(以(i,j)为等腰三角形顶点,三角形内所有点都是前一步可能的点,注意这个等腰三角形和往前走的三角形是倒着的)memset(dp,-0x3f,sizeof dp);dp[0][0]=w[0][0];for(int i=0;i//每一步都有9种走法//往上走3个,往左走3个,斜着走3个for(int a=1;a<=3;a++){if(i>=a)dp[i][j]=max(dp[i][j],dp[i-a][j]);if(j>=a)dp[i][j]=max(dp[i][j],dp[i][j-a]);}if(i>=1&&j>=1)dp[i][j]=max(dp[i][j],dp[i-1][j-1]);if(i>=1&&j>=2)dp[i][j]=max(dp[i][j],dp[i-1][j-2]);if(i>=2&&j>=1)dp[i][j]=max(dp[i][j],dp[i-2][j-1]);if(!(i==0&&j==0))dp[i][j]+=w[i][j];}cout<

5.字符排序(逆序对(难))

在这里插入图片描述
在这里插入图片描述

#include
using namespace std;
int V , len , now , cnt[27] , sum[27];
int get_max(int len){return ((len - (len / 26 + 1)) * (len / 26 + 1) * (len % 26) + (26 - len % 26) * (len / 26) * (len - len / 26)) / 2;
}
bool check(int x , int n){//当前位置i放x字母后,后面的n个位置是否符合要求 memset(cnt , 0 , sizeof(cnt));//cnt记录后面n个位置放置的对应字符数量int add1 = 0 , add2 = 0;for(int j = 26 ; j >= x + 1 ; j --) add1 += sum[j];//1~i-1里边比当前插入字符大的字符数量sum[x] ++ ;//当前字符数量增加1for(int L = 1 ; L <= n ; L ++){//当前位置i放了x,看当前位置i后面剩下的n个位置能不能放好字母使得达到V的条件,这是一个预测过程 //ma保存i位置后面的n个位置能增加的最大逆序对个数 L-1-cnt[j]+num//L-1-cnt[j]是当前字符之后的比当前字符小的字符数量的最大值//num是1~pos+L-1前的比当前放置字符字典序大的字符数量int ma = -1 , pos = 0 , num = 0;for(int j = 26 ; j >= 1 ; j --){//枚举i位置后面的n个位置,每个位置放什么字母 if(L - 1 - cnt[j] + num > ma){//L - 1是算i+1到i+L的个数(不算i+L自己) // - cnt[j] 如果在i+1到当前位置i+L前有放过字母j,然后当前位置i+L也尝试放j,//那当前位置不和前面放j的位置构成逆序对 要减掉前面放j的位置个数 ma = L - 1 - cnt[j] + num;pos = j;//在L这个位置放上字母j }num += sum[j];//num是从1-i位置里大于等于j的数的个数 //因为j是从26逆着放到1的 到下一轮循环时,j--假设记为j',那要求的1-i里比j'大的数就是num //那么这个j'会和前面已经确定好的所有属于26~(j'+1)的数构成逆序对//其实最后的num是算的所有大于等于1的个数,但是没关系,它不会更新到ma里 }add2 += ma , cnt[pos] ++;//L这个位置确定下来,放pos这个字母 }if(now + add1 + add2 >= V) {//now是i这个位置以前的所有逆序对个数,//add1是i位置放进x后,x与前面所有字母构成的逆序对 //add2是i位置放进x后,后面的位置的所有数能增加的逆序对个数,分为2部分,//1、后面位置所有的数与1~i部分的数构成的逆序对个数//2、后面所有位置L,分别与从i+1~i+L-1之间的数构成的逆序对个数,然后这些逆序对的和 now += add1;//now更新i位置确定好放字母x后,从1~i位置所有逆序对个数 return true;}else {//i这个位置放x不能使逆序对个数>=V sum[x] -- ;return false;}
}
signed main()
{string ans = "";cin >> V;for(int i = 1 ; ; i ++) {if(get_max(i) >= V){//i这个长度的字符串最多能有get_max(i)个逆序对 如果get_max(i) >= V,//那么i这个长度就是我们需要的最短长度 len = i;break ;}}for(int i = 1 ; i <= len ; i ++){//按长度从左往右尝试放字母 for(int j = 1 ; j <= 26 ; j ++){//按顺序'a'-'z' 26个字母放   先用1-26代表这26个字母 if(check(j , len - i)){//现在是在第i位放j字母  看i后面的len-i个字母能否符合条件 ans += char(j + 'a' - 1);//符合条件就把j放在i这个位置 break ;//然后尝试放下一个位置 }}}cout << ans << '\n';return 0;
}

6.子串分值和(哈希表+子串小推导)

在这里插入图片描述

思路:
原始思路是枚举所有的子串,然后对每个子串进行求值,最后求和。但是这样会超时。

换个方法:枚举所有字符,累加另包含当前字符的子串数目。

#include 
#include
#include
#includeusing namespace std;const int N = 1e6 + 10,INF=0x3f3f3f3f;
int visited[1000];
long long int sum = 0;
int main()
{string s;cin >> s;memset(visited, -INF, sizeof visited);//我们要算出每个字母的贡献值累加即可//(1)当前字母如果在前面出现过,那么以最近一次出现该字母后面的一个字母为起点,字符串最后一格字符为终点。当前字母将//起点到终点分为两个部分,两个部分相乘即可for (int i = 0; i < s.length(); i++){//枚举到当前字符,以当前字符为中心将字符串分为两个部分long long int L, R;  //L表示左边的字符个数,R右边.左右相乘if (visited[s[i]] <0)  //说明该字符前面没有出现过。{L = i + 1;R = s.length() - i;}else   //否则出先过,应该从最近一次出现的后一个字符作为起始字符{L = i - visited[s[i]];R = s.length() - i;}sum += L * R;   //左右相乘visited[s[i]] = i;   //更新当前字符的位置}cout << sum << endl;return 0;
}

7.游园安排(最长不上升子序列dp)

在这里插入图片描述
在这里插入图片描述

#include 
#include
#include
using namespace std;const int N = 1e5 + 10;
int dp[N];  //dp[i]=j表示
string s[N];   //s[1]表示第一个名字
int path[N];  //存放路径
int a[N];   //存放路径int main()
{string str;cin >> str;int k = -1;for (int i = 0; i < str.length(); i++)  //预处理字符串 ,先预处理一下名字,将其放入一个string数组{if (str[i] >= 'A' && str[i] <= 'Z'){k = k + 1;s[k]+=str[i];}elses[k]+=str[i];}//初始化dpfor (int i = 0; i <= k; i++)  //k个名字dp[i] = 1;memset(path, -1, sizeof path);memset(a, -1, sizeof a);int start = 0;int res = 0;for (int i = 0; i <= k; i++){string temp="";for (int j = 0; j < i; j++){if (s[i]>s[j])  //要严格满足s[i]>s[j]才行{if (dp[j] + 1 > dp[i]){dp[i] = dp[j] + 1;path[i] = j;temp = s[j];  //将最新的s[j]保存}elseif (dp[j] + 1 == dp[i])  //如果等于,取最小的s[j]{if (temp>s[j])  //如果temp>s[j],说明temp比当前的s[j]大,我们要取小的{path[i] = j;temp = s[j];}}}}if (dp[i] > res){res = dp[i];start = i;}elseif (dp[i] == res)  // 可能有相同长度的子序列,需要挑选出字典序最小的{if(s[start]>s[i])  //如果之前的最小字典序的子序列大于等于当前的子序列,我们优先选择小的start = i;} }int i = 0;while (start != -1){a[i++] = start;start = path[start];}for (i = res - 1; i >= 0; i--)cout<

相关内容

热门资讯

嘉兴旅游景点简介及导游词 嘉兴旅游景点简介及导游词  嘉兴,自古为富庶繁华之地,素有“鱼米之乡,丝绸之府”之美誉。嘉兴旅游资源...
董永公园导游词 董永公园导游词范文  各位游客,欢迎光临孝感董永公园,我是(导游词),我代表我们旅行社欢迎大家到汉孝...
介绍趵突泉的导游词 介绍趵突泉的导游词  作为一名乐于助人的导游,常常需要准备导游词,导游词是讲解当地的基本情况,介绍风...
张家口大镜门的英文导游词 张家口大镜门的英文导游词  Hello,everyone!  Welcome to name is ...
云南丽江古城导游词 云南丽江古城导游词 15篇  作为一名乐于为游客排忧解难的导游,总不可避免地需要编写导游词,导游词事...
杭州花港观鱼导游词 杭州花港观鱼导游词范文  作为一名专门引导游客、助人为乐的导游,编写导游词是必不可少的,导游词具有注...
吉林市松花江导游词 吉林市松花江导游词3篇  作为一位尽职的导游,时常要开展导游词准备工作,导游词事实上是一种对旅游景点...
北京圆明园的导游词 北京圆明园的导游词  圆明园位于北京市西郊,海淀区东部。原为清代一座大型皇家御苑,占地约5200亩,...
沙澧公园导游词 沙澧公园导游词  大家好!欢迎大家来到美丽的漯河,来到美丽的沙澧公园。我姓张,今天由我来为大家服务!...
蒋氏故居导游词 蒋氏故居导游词  蒋氏故居位于浙江省宁波市奉化区溪口境内,昔日蒋氏家族就于此地生活,工作,娱乐等。下...
安徽九华山的导游词 有关安徽九华山的导游词范文  九华山在皖南青阳县境内,是我国四大佛教名山之一。唐代文学家刘禹锡,登上...
阳龙导游词 阳龙导游词  游客朋友们大家好,今天我们一起来游览具有“天下第一缸”之称的云阳龙缸,我是大家今天行程...
世界地质奇观—阿斯哈图花岗岩... 世界地质奇观—阿斯哈图花岗岩石林的导游词  女士们、先生们:大家好!现在我们已经来到了国家4A级旅游...
洛阳牡丹导游词 洛阳牡丹导游词  作为一名具备丰富知识的导游,总归要编写导游词,导游词是导游员进行实地口语导游的基础...
常州恐龙园导游词 常州恐龙园导游词500字  作为一名专门为游客提供优质服务的导游人员,通常会被要求编写导游词,导游词...
河南天波杨府的导游词 河南天波杨府的导游词  天波杨府是北宋抗辽英雄杨业的府邸,原位于北宋首都东京(今开封)城内西北偶、天...
黄山松导游词 黄山松导游词  导游词是导游人员引导游客观光游览时的讲解词,是导游员同游客交流思想,向游客传播文化知...
龙门石窟导游词 龙门石窟导游词(通用21篇)  作为一名专门引导游客、助人为乐的导游,就不得不需要编写导游词,借助导...
韩国釜山导游词 关于韩国釜山导游词  各位游客,大家好,欢迎大家来到这韩国南部以"深水良港"而著称的釜山参观游览,我...
云南抚仙湖导游词 云南抚仙湖导游词  作为一位出色的导游人员,往往需要进行导游词编写工作,导游词由引言、主体和结语三部...