bzoj 1026 [SCOI2009]windy数 题解(数位DP)

本文详细解析了SCOI2009中关于Windy数的问题,介绍了如何利用数位DP来解决[l, r]区间内满足条件的Windy数的数量。通过推导DP方程和进行数位拆分,阐述了计算方法和具体实现过程。" 139582346,8596454,Linux Kernel 5.4: 添加GPU与Framebuffer实战,"['Linux内核', '嵌入式开发', 'GPU驱动', 'DRM框架', 'virtio设备', 'RISC-V架构']
摘要由CSDN通过智能技术生成

原题链接:
洛谷:点我QωQ
bzoj:点我QωQ

题意简述

定义一个 W i n d y Windy Windy数为:一个 W i n d y Windy Windy数没有前导零并且所有相邻的两位差的绝对值都 > = 2 >=2 >=2。给定 l , r l,r l,r [ l , r ] [l,r] [l,r]之间有多少 W i n d y Windy Windy数。

数据

输入

一行两个正整数 l , r l,r l,r

输出

l , r l,r l,r中有多少 W i n d y Windy Windy数。

样例

输入
1 10
输出
9

输入
25 50
输出
20

思路

显然数位 D P DP DP。我个人认为,数位 D P DP DP不难在 D P DP DP方程,而是难在数位拆分。

part1. D P DP DP方程推导

d p [ i ] [ j ] dp[i][j] dp[i][j]为:位长度为 i i i,首位是 j j j,有多少 W i n d y Windy Windy数。
那么 d p [ i ] [ j ] = d p [ i − 1 ] [ k ] ( ∣ k − j ∣ > = 2 ) dp[i][j]=dp[i-1][k]\quad(|k-j|>=2) dp[i][j]=dp[i1][k](kj>=2),这很显然。也就是我们枚举一个满足条件的点作为上一个状态。

part2. 数位拆分

我们设一个 c a l c ( x ) calc(x) calc(x),表示 [ 1 , x ] [1,x] [1,x]中有多少 W i n d y Windy Windy数。那么答案 = c a l c ( r ) − c a l c ( l − 1 ) =calc(r)-calc(l-1) =calc(r)calc(l1)。那么问题来了:如何求 c a l c ( x ) calc(x) calc(x)?
我们通过举例子的方法理解。设 x = 654 x=654 x=654。那么,答案分为几类呢?

  1. 首位是 [ 1 , 5 ] [1,5] [1,5],剩下的位随便取。此时的答案是 d p [ 3 ] [ 1...5 ] dp[3][1...5] dp[3][1...5]
  2. 位数 &lt; 3 &lt;3 <3。此时的答案为 d p [ 2 ] [ 1...9 ] + d p [ 1 ] [ 1..9 ] dp[2][1...9]+dp[1][1..9] dp[2][1...9]+dp[1][1..9]
  3. 654 654 654 1...3 1...3 1...3为前缀,剩下的位 &lt; 654 &lt;654 <654(即 6 ∗ ∗ 6** 6 65 ∗ 65* 65 654 654 654,其中 ∗ * 表示小于后面位的任意)。

我们把它广义。设 x x x分解完有 c n t cnt cnt位,第 i i i位(全网为数不多的从左到右数的)为 d [ i ] d[i] d[i]。那么

  1. 也就是 d p [ c n t ] [ 1... ( d [ 1 ] − 1 ) ] dp[cnt][1...(d[1]-1)] dp[cnt][1...(d[1]1)]
  2. 也就是 d p [ 2... c n t ] [ 1...9 ] dp[2...cnt][1...9] dp[2...cnt][1...9]
  3. 就比较复杂了,单独拿出来讲。

我们枚举前缀长度 i i i 1 1 1 c n t − 1 cnt-1 cnt1。枚举一个 j j j使得 ∣ j − d [ i ] ∣ &gt; = 2 |j-d[i]|&gt;=2 jd[i]>=2并且 j &lt; d [ i + 1 ] j&lt;d[i+1] j<d[i+1]。其中 ∣ j − d [ i ] &gt; = 2 ∣ |j-d[i]&gt;=2| jd[i]>=2保证满足条件, j &lt; d [ i + 1 ] j&lt;d[i+1] j<d[i+1]保证我们枚举出来的数 &lt; = x &lt;=x <=x。这样枚举 j j j后,加入 d p [ c n t − i ] [ j ] dp[cnt-i][j] dp[cnti][j]到答案。其中 c n t − i cnt-i cnti表示剩下 c n t − i cnt-i cnti位, j j j表示我们枚举剩下的首位为 j j j。当然,我们枚举的这个前缀要满足条件。即:如果 1... i 1...i 1...i中有差绝对值 &lt; 2 &lt;2 <2的就直接 b r e a k break break。当然,不用每次都查,只要在我们枚举 i i i的时候,判断下一次会不会 b r e a k break break即珂。当然还要特判 x x x。如果我们枚举的 i = = c n t − 1 i==cnt-1 i==cnt1,说明搞完了,而且没有被 b r e a k break break掉,那么说明 x x x本身也是 W i n d y Windy Windy数,我们没有算到,故 + + a n s ++ans ++ans

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 20
    int dp[N][N];
    void DP()
    {
        for(int i=0;i<=9;++i) dp[1][i]=1;//明显边界
        int n=11;
        for(int i=2;i<=n;++i)
        {
            for(int j=0;j<=9;++j)
            {
                for(int k=0;k<=9;++k)
                {
                    if (abs(k-j)>=2)
                    {
                        dp[i][j]+=dp[i-1][k];//明显的转移
                    }
                }
            }
        }
    }
    int calc(int x)
    {
        int d[N];int cnt=0;
        while(x!=0)
        {
            d[++cnt]=x%10;;
            x/=10;
        }reverse(d+1,d+cnt+1);//我是正着数的,所以reverse
 
        int ans=0;
        for(int i=2;i<=cnt;++i)//情况2.
        {
            for(int j=1;j<=9;++j)
            {
                ans+=dp[cnt-i+1][j];
            }
        }
        for(int i=1;i<d[1];++i)//情况1.
        {
            ans+=dp[cnt][i];
        }
        for(int i=1;i<cnt;++i)//情况3.
        {
            for(int j=0;j<d[i+1];++j)
            {
                if (abs(d[i]-j)>=2)
                {
                    ans+=dp[cnt-i][j];
                }
            }
            if (abs(d[i]-d[i+1])<2) break;
            if (i==cnt-1) ++ans;
        }
        return ans;
    }
 
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        DP();
        int l,r;scanf("%d%d",&l,&r);
        printf("%d\n",calc(r)-calc(l-1));
    }
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值