• 理想正方形(单调队列),22数(数位dp)


    1.洛谷[HAOI2007]理想的正方形 (单调队列)

    原题链接

    题目描述

      有一个 a × b a \times b a×b 的整数组成的矩阵,现请你从中找出一个 n × n n \times n n×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

      输入格式

      第一行为 3 3 3 个整数,分别表示 a , b , n a,b,n a,b,n 的值。

      第二行至第 a + 1 a+1 a+1 行每行为 b b b 个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。

      输出格式

      仅一个整数,为 a × b a \times b a×b 矩阵中所有“ n × n n \times n n×n 正方形区域中的最大整数和最小整数的差值”的最小值。

      样例

      样例输入

      5 4 2

      1 2 5 6

      0 17 16 0

      16 17 2 1

      2 10 2 1

      1 2 2 2

      样例输出

      1

      提示

      问题规模。

      矩阵中的所有数都不超过 1 , 000 , 000 , 000 1,000,000,000 1,000,000,000

       20 % 20\% 20% 的数据 2 ≤ a , b ≤ 100 , n ≤ a , n ≤ b , n ≤ 10 2 \le a,b \le 100,n \le a,n \le b,n \le 10 2a,b100,na,nb,n10

       100 % 100\% 100% 的数据 2 ≤ a , b ≤ 1000 , n ≤ a , n ≤ b , n ≤ 100 2 \le a,b \le 1000,n \le a,n \le b,n \le 100 2a,b1000,na,nb,n100


      滑动窗口求最大最小值,对于每一行吧每个长度为n的区间的最值求出,此时可以看作每个长度为n的区间内的最值位于该区间最后一个位置,然后再在列上对这些最值求一次最大最小得到该n*n区间内正方形最值,最后维护ans=min(ans,b[i]-c[i])。

    #include 
    #include 
    using namespace std;
    const int maxn=1010;
    int n,m,k;
    int row_max[maxn][maxn],row_min[maxn][maxn];//行的最大最小值
    int q[maxn],w[maxn][maxn];//单调队列,矩阵
    void get_min(int a[],int b[],int len){
        int hh=0,tt=-1;
        for(int i=1;i<=len;++i){
            if(hh<=tt&&i-k>=q[hh]) ++hh;
            while(hh<=tt&&a[q[tt]]>=a[i]) --tt;
            q[++tt]=i;
            b[i]=a[q[hh]];
        }
        //求最小值维护递增单调递增的单调队列
        //可用区间为从i开始往前的k个元素即[i-k+1,i]
    }
    void get_max(int a[],int b[],int len){
        int hh=0,tt=-1;
        for(int i=1;i<=len;++i){
            if(hh<=tt&&i-k>=q[hh]) ++hh;
            while(hh<=tt&&a[q[tt]]<=a[i]) --tt;
            q[++tt]=i;
            b[i]=a[q[hh]];
        }
        //求最大值维护单调递减的单调队列
        //可用区间为从i开始往前的k个元素即[i-k+1,i]
    }
    
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                scanf("%d",&w[i][j]);
            }
        }
        for(int i=1;i<=n;++i){
            get_min(w[i],row_min[i],m);
            get_max(w[i],row_max[i],m);
        }
        int a[maxn],b[maxn],c[maxn];
        int ans=0x3f3f3f3f;
        for(int i=k;i<=m;++i){
            for(int j=1;j<=n;++j) a[j]=row_max[j][i];
            get_max(a,b,n);
            for(int j=1;j<=n;++j) a[j]=row_min[j][i];
            get_min(a,c,n);
            //列元素不连续先吧每列元素放到临时数组a中,b中维护最大值,c中维护最小值
            for(int j=k;j<=n;++j){
                ans=min(ans,b[j]-c[j]);
            }//从[k,k]开始往后枚举所有的最值之差并求最小
        }
        printf("%d\n",ans);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2.牛客网.22数(数位dp)

    原题链接


      题目描述

      现在定义一种22数, 这种数各个数位上的数字之和等于其位数的二倍

      求 1 ~ n 中,22数的个数 x。

      输入描述:

      输入包含一行一个正整数 n ( 1 ≤ n ≤ 1 0 10 ) n(1 \leq n \leq 10^{10}) n(1n1010)

      输出描述:

      输出包含一行一个数表示 1 ∼ n 1 \sim n 1n范围内22数的个数 x。

       示例

      输入 :22

      输出: 3

      说明

      1~22范围内包含3个22数分别是2,13,22。


       dp[i]j][k] 表示数位为i,最高位为j,数位和为k的所有方案数。先预处理出所有的dp数组, 1 0 10 10^{10} 1010最多有十个数位,所以k理论上最多为20,保险起见可以多开,同时爆了int,本题所有数组均需要开long long

       dp的预处理考虑数位为i的时候最高位为j,总和为k,他会从dp[i-1][t][k-j]转移过来(k>j, 0<=t<=9)

    void init(){
        for(int i=0;i<=9;++i){
            f[1][i][i]++;
        }
        for(int i=1;i<=10;++i){// n
            for(int j=0;j<=9;++j){// nmax
                for(int k=0;k<=20;++k){ //sum
                    for(int t=0;t<=9;++t) //i-1 
                    if(k>=j){
                        f[i][j][k]+=f[i-1][t][k-j];
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15


       对于之后的方案数,枚举所有数位,先将其各位拆分出来,然后对于每个数位上数字x有两种可能:

       第一:该位选取0-x-1范围内的数字,那么ans+=dp[i+1][j][n*2-last](从i到0有i+1位数字,除最高位外0<=j
       第二:该位选取x,那么last+=x,当i==0时,如果n * 2 == last,++ans


       该过程中如果出现了last>=n*2的情况可以直接break


       这是所有不含前导0的情况,最后再额外处理除前导0的情况即可,从第1位到第n-1位,每一位枚举1-9, ans+=dp[i][j][i*2]

    #include 
    using namespace std;
    typedef long long ll;
    ll f[15][15][100];//i->n j->n-max  k ->sum
    ll n;
    void init(){
        for(int i=0;i<=9;++i){
            f[1][i][i]++;
        }
        for(int i=1;i<=10;++i){// n
            for(int j=0;j<=9;++j){// nmax
                for(int k=0;k<=20;++k){ //sum
                    for(int t=0;t<=9;++t) //i-1 
                    if(k>=j){
                        f[i][j][k]+=f[i-1][t][k-j];
                    }
                }
            }
        }
    }
    ll dp(ll x){
        vector<ll> tmp;
        while(x) tmp.push_back(x%10),x/=10;
        ll n=tmp.size();
        ll ans=0;
        ll last=0;
        for(int i=tmp.size()-1;i>=0;--i){
            for(int j=i==tmp.size()-1;j<tmp[i];++j){
                ans+=f[i+1][j][2*n-last];
            }
            last+=tmp[i];
            if(last>2*n) break;
            if(i==0&&last==n*2) ans++;
        }
        for(int i=1;i<n;++i){
            for(int j=1;j<=9;++j){
                ans+=f[i][j][i*2];
            }
        }
        return ans;
    }
    
    int main()
    {
        init();
        cin>>n;
        cout<<dp(n);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  • 相关阅读:
    【Filament】基于物理的光照(PBR)
    多层神经网络和激活函数
    [含毕业设计论文+PPT+源码等]ssm后勤服务|会议室预约|办公管理管理系统小程序+Java后台管理系统|前后分离VUE
    【小程序websocket前后端交互】uniapp写微信小程序聊天功能功能,websocket交互功能,心跳重连【详细注释,复制即用】
    前端---CSS的盒模型
    Java_题目_学生管理系统_业务分析并搭建主菜单_查询添加删除修改
    基于AERMOD模型在大气环境影响评价中的实践技术
    Android 查看路由表
    Ribbon负载均衡
    Spring之AOP-JDK动态代理源码解析
  • 原文地址:https://blog.csdn.net/m0_67739923/article/details/125892018