蓝桥杯国赛训练第二周

  • 1|0Coloring Brackets

一道区间DP好题

一开始以为有多种不同的括号匹配次序而导致自己一头大雾wuw,首先看到括号匹配就要想到用栈来求出每个括号对应的匹配项,对于一个区间来说,其左括号一定是具有与之对应的右括号存在时染色才有意义,所以我们要求出每个括号对应的位置should[i],首先设出状态表达式为:dp[l][r][i][j] 也就是 l r 区间,两端点的染色方案为ij

接下来考虑如何求出方案数:
对于一个相邻的完整括号即:() 
这种我们的染色的方案数一定是对于每种情况都是一种,即:

dp[l][r][0][1]=dp[l][r][1][0]=dp[l][r][2][0]=dp[l][r][0][2]=1

接下来考虑左括号与右括号相匹配,那么这种情况我们要求在这个区间内的所有括号的染色方案数均被求出,这样我们才能向外扩展,也就是从l+1扩展到l,对于这种情况下的状态转移如何求?很明显我们只需要考虑某一个端点中的相邻括号的颜色即可,这里拿左端点为例:若左端点l为颜色1,那么其l+1的颜色不可为1,那么此时我们有:

dp[l][r][1][0]=dp[l+1][r1][2][0]+dp[l+1][r1][0][1]+dp[l+1][r1][0][2]

其它情况同理

对于左端点和右端点的括号不匹配,那么我们就需要寻找左端点对应匹配的位置,并且其相邻位置不应该同色,那么就有:

for(inti=0;i<=2;i++)
for(intj=0;j<=2;j++)
for(inte=0;e<=2;e++)
for(intk=0;k<=2;k++)
if(j==eandj>0)continue
dp[l][r][i][k]=(dp[l][r][i][k]+dp[l][should[l]][i][j]dp[should[l]+1][r][e][k]
为什么是相乘呢?因为左区间所有的情况只适用于右区间的一种情况,也就是乘法原理,不懂的可以去看下,至此就结束了

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1000,mod=1e9+7; int dp[N][N][5][5]; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); string s; cin>>s; stack<int>stk; int n=s.size(); vector<int>should(n+1); for(int i=0;i<n;i++){ if(s[i]=='(') stk.push(i+1); else should[i+1]=stk.top(),should[stk.top()]=i+1,stk.pop(); } function<void(int,int)>dfs; dfs=[&](int l,int r)->void{ if(l==r-1) dp[l][r][0][1]=dp[l][r][1][0]=dp[l][r][2][0]=dp[l][r][0][2]=1; else if(r==should[l]){ dfs(l+1,r-1); for(int i=0;i<=2;i++){ for(int j=0;j<=2;j++){ if(i!=1) dp[l][r][1][0]=(dp[l][r][1][0]+dp[l+1][r-1][i][j])%mod; if(i!=2) dp[l][r][2][0]=(dp[l][r][2][0]+dp[l+1][r-1][i][j])%mod; if(j!=1) dp[l][r][0][1]=(dp[l][r][0][1]+dp[l+1][r-1][i][j])%mod; if(j!=2) dp[l][r][0][2]=(dp[l][r][0][2]+dp[l+1][r-1][i][j])%mod; } } } else{ dfs(l,should[l]),dfs(should[l]+1,r); for(int i=0;i<=2;i++){ for(int j=0;j<=2;j++){ for(int e=0;e<=2;e++){ for(int k=0;k<=2;k++){ if(j&&e&&j==e) continue; dp[l][r][i][k]=(dp[l][r][i][k]+dp[l][should[l]][i][j]*dp[should[l]+1][r][e][k]%mod)%mod; } } } } } }; dfs(1,n); int res=0; for(int i=0;i<=2;i++) for(int j=0;j<=2;j++) res=(res+dp[1][n][i][j])%mod; cout<<res<<'\n'; return 0; }
  • 2|0Shuffling Songs

这是我比赛的时候遇到的一道题目,当时没做出来,首先先看数据范围,这么小大概率是状压,然后考虑如何转移,对于所有的状态,我们可以枚举以歌曲j结尾时候的方案数,那么显然设出状态表达:dp[i][j] 为 状态 i 的时候以j结尾的方案数,然后枚举状态即可,如果状态i中含有歌曲或作者j那么直接跳过,如果不包含且两者可以合并,那么就让j做为新的结尾,并且方案数加1,如何求出方案数?直接看二进制状态中有多少个1即可

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; void solve(){ int n; cin>>n; vector<string>s(n+1),g(n+1); for(int i=1;i<=n;i++) cin>>s[i]>>g[i]; vector<vector<bool>>ok(n+1,vector<bool>(n+1,false)); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(s[i]==s[j]||g[i]==g[j]) ok[i][j]=true; //两个可以加,标记一下 } } vector<vector<bool>>dp(1<<n|1,vector<bool>(n+1,false)); int res=0; for(int i=1;i<=n;i++) dp[1<<(i-1)][i]=true; for(int k=0;k<(1<<n);k++){ for(int i=1;i<=n;i++){ if(dp[k][i]){ //若k状态可以以i结尾,那么考虑可不可以用继续加 res=max(res,(int)__builtin_popcount(k));//先把此时的方案数求出 for(int j=1;j<=n;j++){ if(!(k&(1<<(j-1)))&&ok[i][j]) dp[k|(1<<(j-1))][j]=true; //可以加,就加 } } } } cout<<n-res<<'\n'; } signed main(){ std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int t;cin>>t;while(t--)solve(); }
  • 3|0Rudolf and k Bridges

一眼单调队列优化dp,为什么?因为很明显我们在建桥的过程中要尽可能的选消耗小的材料,那么对于一段区间[id,i]我们肯定要选择最小的消耗,那么就需要单调队列来维护了,由于需要求出连续的值,用前缀和维护一下即可

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; void solve(){ int n,m,k,d; cin>>n>>m>>k>>d; d++; vector<int>num(n+1); int res=1e18; for(int i=1;i<=n;i++){ vector<int>a(m+1); for(int j=1;j<=m;j++) cin>>a[j],a[j]++; vector<int>q(m+1),dp(m+1,1e18); dp[1]=1; int hh=1,tt=0; for(int j=2;j<=m;j++){ while(hh<=tt&&j-q[hh]>d) hh++; while(hh<=tt&&dp[j-1]<=dp[q[tt]]) tt--; q[++tt]=j-1,dp[j]=dp[q[hh]]+a[j]; } num[i]=num[i-1]+dp[m]; if(i>=k) res=min(res,num[i]-num[i-k]); } cout<<res<<'\n'; } signed main(){ std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int t;cin>>t;while(t--)solve(); }
  • 4|0[USACO11OPEN] Mowing the Lawn G

首先考虑暴力,设我们当前在位置i,那么我们此时能够获得的最大贡献为dp[i],且有:
我们可以在区间 [iK,i] 间枚举休息的奶牛,所以转移方程就可以初步推出

dp[i]=max{dp[j1]+k=j+1iEk}   (iKji)

用前缀和优化一下的话就是:

dp[i]=max{dp[j1]+Sum[i]Sum[j]}   (iKji)

此时的复杂度为O(nk)很明显还是不可以
由于sum[i]不变,只需要求出[ik,i]这段区间最大的dp[j1]+sum[j]即可,故使用单调队列优化即可

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,k; cin>>n>>k; vector<int>e(n+1),s(n+1); for(int i=1;i<=n;i++) cin>>e[i],s[i]=s[i-1]+e[i]; vector<int>q(n+1),dp(n+1),_(n+1); int hh=1,tt=0; for(int i=1;i<=n;i++){ while(hh<=tt&&i-q[hh]>k) hh++; while(hh<=tt&&dp[i-1]-s[i]>=_[q[tt]]) tt--; q[++tt]=i,_[i]=dp[i-1]-s[i]; if(i>k) dp[i]=_[q[hh]]+s[i]; else dp[i]=s[i]; } cout<<dp[n]<<'\n'; return 0; }
  • 5|0重建道路

给出一些子树,很明显我们可以求出所有子树的大小,那么我们就能把题意转化为:选择一些子树,使得这些子树的大小恰好等于k且尽可能的小.这不就是背包吗?所以我们要进行树上背包:一开始我是求错了,我直接对每个子树都进行了一遍背包,也就是dpi是剪掉i大小所用的最小分支,当时就直接过了,但是我越想越不对,越来越感觉写假了,果然,我看了看其他人的讨论,我应该就是写假了,因为如果我不考虑子树的话,很有可能造成重复计算,考虑一个例子:1-2,2-3,3-4,1-5,1-6,1-7,1-8,1-9,1-10.要求出大小为4的分支,答案应该是4,但我输出是3,因为我的dp[2]dp[3] 均在一个子树上,而一个子树不能被用两次,所以我们要这样设:dp[u][i] 以u为根节点的树,保留i个节点所需要的最小分支数,然后对所有的子树做一遍背包即可

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,p; cin>>n>>p; vector<int>g[n+1],in(n+1); for(int i=1;i<n;i++){ int a,b; cin>>a>>b; g[a].push_back(b),in[a]++; } vector<vector<int>>dp(300,vector<int>(300,1e8)); for(int i=1;i<=n;i++) dp[i][1]=in[i]; vector<int>sz(n+1); function<void(int,int)>dfs; int res=1e8; dfs=[&](int u,int fa)->void{ sz[u]=1; for(auto x:g[u]){ if(x==fa) continue; dfs(x,u); sz[u]+=sz[x]; for(int i=sz[u];i>=1;i--){ for(int j=1;j<i;j++){ dp[u][i]=min(dp[u][i],dp[u][i-j]+dp[x][j]-1); } } } res=min(res,dp[u][p]+(u==1?0:1)); }; dfs(1,-1); cout<<res<<'\n'; return 0; }
  • 6|0[USACO12MAR] Cows in a Skyscraper G

对于这道题目,根据数据范围考虑状压,设dp[k]为状态为k的时候用到的最小分组数,__[k]为状态为k下,此时背包还有多少空间可以用,如果当前的状态没有物品i,那么如果加入物品i之后分组数更小,即:dp[i|(1<<(j-1))]>dp[i]+(_[i]<a[j]) 我们就可以转移,但是我们还有背包空间的限制,所以说我们要取等号,看看相同分组数的情况下,谁的剩余空间更大就选谁.如果背包空间足够大,那么就有:_[i|(1<<(j-1))]=max(_[i|(1<<(j-1))],_[i]-a[j]) 否则我们需要加一个新的背包:_[i|(1<<(j-1))]=max(_[i|(1<<(j-1))],k-a[j])

#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,k; cin>>n>>k; vector<int>a(n+1); for(int i=1;i<=n;i++) cin>>a[i]; vector<int>dp(1<<n|1,1e8),_(1<<n|1); dp[0]=1,_[0]=k; for(int i=0;i<(1<<n);i++){ for(int j=1;j<=n;j++){ if((1<<(j-1))&i) continue; if(dp[i|(1<<(j-1))]>=dp[i]+(_[i]<a[j])){ dp[i|1<<(j-1)]=dp[i]+(_[i]<a[j]); if(_[i]>=a[j]) _[i|(1<<(j-1))]=max(_[i|(1<<(j-1))],_[i]-a[j]); else _[i|(1<<(j-1))]=max(_[i|(1<<(j-1))],k-a[j]); } } } cout<<dp[(1<<n)-1]; return 0; }

__EOF__

本文作者Sakurajimamai
本文链接https://www.cnblogs.com/o-Sakurajimamai-o/p/18199317.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   o-Sakurajimamai-o  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
-- --
点击右上角即可分享
微信分享提示