首页 > 其他分享 >蓝桥杯国赛训练第二周

蓝桥杯国赛训练第二周

时间:2024-05-18 14:30:21浏览次数:24  
标签:括号 杯国赛 long 蓝桥 int hh 第二周 dp mod

  • H 重建道路

一道区间DP好题

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

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

\(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][r-1][2][0]+dp[l+1][r-1][0][1]+dp[l+1][r-1][0][2]\)

其它情况同理

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

\(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 and j>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]%mod)%mod\)
为什么是相乘呢?因为左区间所有的情况只适用于右区间的一种情况,也就是乘法原理,不懂的可以去看下,至此就结束了

#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;
}
  • Shuffling 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();
}
  • Rudolf and k Bridges

一眼单调队列优化\(dp\),为什么?因为很明显我们在建桥的过程中要尽可能的选消耗小的材料,那么对于一段区间\([i-d,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();
}
  • [USACO11OPEN] Mowing the Lawn G

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

\[dp[i]=max\{dp[j-1]+\sum\limits_{k=j+1}^{i}E_k\}\ \ \ (i-K\leq j\leq i) \]

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

\[dp[i]=max\{dp[j-1]+Sum[i]-Sum[j]\}\ \ \ (i-K\leq j\leq i) \]

此时的复杂度为\(O(n*k)\)很明显还是不可以
由于\(sum[i]\)不变,只需要求出\([i-k,i]\)这段区间最大的\(dp[j-1]+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;
}

标签:括号,杯国赛,long,蓝桥,int,hh,第二周,dp,mod
From: https://www.cnblogs.com/o-Sakurajimamai-o/p/18199317

相关文章

  • P8684 [蓝桥杯 2019 省 B] 灵能传输 题解
    题目传送门本题涉及到了\(3\)种算法:前缀和,排序以及贪心。(1)前缀和本题实际上要求通过某种灵能传输可以使得该序列的最大值最小。而由前缀和可知,当某一个前缀和序列保持有序(或前缀和序列表示的函数单调)时,其\(s[i]-s[i-1]\)的最大值可以达到最小。通过对几个样例的观察......
  • P8741 [蓝桥杯 2021 省 B] 填空问题 题解
    题目传送门试题A:空间【解析】本题考察计算机存储的基础知识,只要掌握空间存储的换算方法,就能够算出答案。【程序】#include<bits/stdc++.h>usingnamespacestd;intmain(){printf("%d\n",256*8/32*1024*1024);return0;}【答案】67108864......
  • P8679 [蓝桥杯 2019 省 B] 填空问题 题解
    题目传送门试题A:组队【解析】本题是一道经典的DFS搜索题,每次对各号位的选手进行DFS,找到各号位上有成绩的选手时再进行进一步搜索即可。【程序】#include<bits/stdc++.h>usingnamespacestd;intteam[20][6];intmax_sum;boolvis[20];voiddfs(intu,intsu......
  • P8806 [蓝桥杯 2022 国 B] 搬砖
    P8806[蓝桥杯2022国B]搬砖一、问题简析本题采用贪心+01背包。令\(a_i=\)第\(i\)块砖;\(a_i.w=\)\(a_i\)的质量;\(a_i.v=\)\(a_i\)的价值。本题与01背包模板不同的地方是,本次选择的砖块会对后续的选择产生影响。为了使承重能力强的砖块留在最后选,贪心地优先......
  • 蓝桥杯-航班时间(简单写法+sscanf的应用)
    小h前往美国参加了蓝桥杯国际赛。小h的女朋友发现小h上午十点出发,上午十二点到达美国,于是感叹到“现在飞机飞得真快,两小时就能到美国了”。小h对超音速飞行感到十分恐惧。仔细观察后发现飞机的起降时间都是当地时间。由于北京和美国东部有12小时时差,故飞机总共需要......
  • P8803 [蓝桥杯 2022 国 B] 费用报销
    P8803[蓝桥杯2022国B]费用报销一、问题简析本题是一道01背包。重点是\(K\)的限制,使我们在取\(a_i\)时,不能无所顾忌地套用模板\(dp_{i-1,j-a_i.worth}+a_i.worth\)。我们必须找到上一张合法的\(a_j\),即\(a_j\)与\(a_i\)之间的日期不小于\(K\)。通过以下步骤确......
  • 蓝桥杯-日期问题
    小明正在整理一批历史文献。这些历史文献中出现了很多日期。小明知道这些日期都在1960年1月1日至2059年12月31日。令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在......
  • 蓝桥杯-移动距离(最简单的写法)
    X星球居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为1,2,3…当排满一行时,从下一行相邻的楼往反方向排号。比如:当小区排号宽度为6时,开始情形如下:123456121110987131415.....我们的问题是:已知了两个楼号m和n,需要求出它们之间的最短移......
  • P9425 [蓝桥杯 2022 国 B] 2022
    一、题目描述将\(2022\)拆分成\(10\)个互不相同的正整数之和,有多少种方案?二、问题简析令\(dp[i][j]=\)\(i\)的\(j\)划分的方案数(满足互不相同的正整数)。有两种实现方式:\(dp[i][j]\)不含\(1\)在\(dp[i-j][j]\)的基础上,每个元素+1。有\(j\)个元素,每个元素+1,......
  • 第十二届蓝桥杯选拔赛 python
    第一题(难度系数2,18个计分点) 编程实现:输入一个正整数n,计算出n乘100的积。 输入描述:输入一个正整数n输出描述:输出n乘100的积 样例输入:2样例输出:200  第二题(难度系数3,20个计分点) 编程实现:给定一个正整数,判断这个正整......