$SCOI2009\ windy$数 数位$dp$

\(Sol\)

数位\(dp\)常规套路题.

\(dp[i][j]\)表示从低位到高位填到第\(i\)位且第\(i\)位的数字为\(j\)的方案数.答案就是\(sol(r)-sol(l+1).\)这里\(dp\)的过程十分简单,一般出错的也就是计算小于等于\(x\)\(windy\)数,所以这里简述一下这个流程:

首先是预处理出\(x\)的位数\(ct\),\(a[i]\)表示数\(x\)从低到高第\(i\)位的数字是多少,然后累计第一种安全态的答案\(as+=\sum _{i=1}^{ct-1}\sum _{j=1}^{9}f[i][j]\).

接着累计第二种安全态的答案,也就是第\(ct\)位(从低到高)的数小于\(a[ct]\)的,\(as+=\sum_{j=1}^{a[ct]-1}f[ct][j]\).

最后也就是最容易出错的地方便是统计危险态的答案.假设当前填到第\(i\)位且\(i\)\(ct\)的数字都和\(x\)一样,那么第\(i-1\)位上填的数就有两个限制,一是要小于\(a[i-1]\),还有就是要与\(a[i]\)的差的绝对值要大于等于\(2\).

会发现上面的统计有两个漏洞,一是如果数\(x\)\(windy\)数,那么会少算一个,这个问题很好解决,判断一下就好.令一个问题是假设当前填到\(i\),且\(abs(a[i]-a[i-1])<=2\),那么下一位就不能填\(a[i-1]\),那么也就不用继续统计危险态的答案了,直接\(break\)掉.

\(Code\)

Code

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
int f[20][10],a[20];
il void init()
{
    go(i,0,9)f[1][i]=1;
    go(i,2,10)
    go(j,0,9)
    go(k,0,9)
    if(abs(j-k)>=2)f[i][j]+=f[i-1][k];
}
il int sol(Ri x)
{
    if(!x)return 0;
    Ri qvq=x,ct=0,ret=0;bool fl=1;
    while(qvq){a[++ct]=qvq%10;qvq/=10;}
    go(i,1,ct-1)
    go(j,1,9)ret+=f[i][j];
    go(i,1,a[ct]-1)ret+=f[ct][i];
    yes(i,ct,2)
    {
    go(j,0,a[i-1]-1)
    {
        if(abs(j-a[i])>=2)ret+=f[i-1][j];
    }
    if(abs(a[i]-a[i-1])<2){fl=0;break;}
    }
    return ret+fl;
}
int main()
{
    init();
    Ri l=read(),r=read();
    printf("%d\n",(sol(r)-sol(l-1)));
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/forward777/p/11592770.html