数位 DP 入门 (不要 62+windy 数)

$$I$$

平常的做法是设 \(f_{i,j}\)\(0\)~\(j \times 10^{i-1}\) 的合法个数,这里用某种神奇而快速的做法。

简化题意:

  • 不要 \(6\ 2\) 连在一起的数字。
  • 不要有 \(4\) 的数字。

我们暂且设 :

  • \(f_{i,1}\)\(0\)~\(10^i-1\) 的合法个数。
  • \(f_{i,2}\)\(0\)~\(10^i-1\) 的不合法个数。
  • \(f_{i,3}\)\(0\)~\(10^i-1\) 的开头为 \(2\) 的合法个数。

我们考虑到要求 \(62\) 这种东西,所以要用 \(f_{i,3}\)
其实这三个数组可以缩成一个,就是 \(f_{i,1}\)

为了方便我们把 \(f_{i,3}\) 去掉。

考虑到 \(f_{i,3}\) 要求合法,且开头是 \(2\)。所以我们用上一位合法的开头加上一个 \(2\) 就是转移。

\[f_{i,3}=f_{i-1,1} \]

考虑合法的如何转移。因为 \(6\ 2\) 会导致不合法,而现在可以给开头加上一个 \(6\),所以我们只需要减去一个上一位的开头为 \(2\) 的个数。

\[f_{i,1}=-f_{i-2,1} \]

然后考虑到上一次合法的数字转移过来只有开头加上 \(4\)\(GG\),所以加上一个 \(f_{i-1,1} \times 9\)

\[f_{i,1}=f_{i-1,1} \times 9-f_{i-2,1} \]

然后考虑 \(f_{i,2}\) 的转移。我上一位的不合法数字在开头上面加上什么都是不合法的。且我可以给合法的数字加上一个 \(4\) 使其不合法。再且我可以给那些以 \(2\) 开头的数字加上一个 \(6\) 使其不合法。所以就是:

\[f_{i,2}=f_{i-1,2} \times 10+f_{i-1,1}+f_{i-2,1} \]

预处理时间复杂度 \(O(k)\),\(k\) 为位数。

查询就更简单了~

首先我们要把上一位的不合法减掉:

\[ans-=num_i\times f_{i-1,2} \]

如果我的数字本身出现了不合法,那么我后面的所有数字都是不合法的。\(num_i\times f_{i-1,2}\) 已经减过了,就减 \(num_i \times f_{i-1,1}\)

\[ans-=num_i \times f_{i-1,1} \]

如果数字本身没有不合法,那么看情况讨论。

  • 如果出现 \(4\),那么上一位的所有合法都是废的。

\[ans-=f_{i-1,1} \]

  • 如果出现 \(6\),那么上一位所有 \(2\) 开头的都是废的。

\[ans-=f_{i-2,1} \]

  • 如果我现在存在了一个 \(6\ 2\),那么这一位所有的 \(2\) 都是废的。

\[ans-=f_{i-1,1} \]

最后统计一下数字本身是否合法。

var
    fuck:array[-1..15,-1..3] of longint;
    i,j,x,y:longint;

function Slove(ans:longint):longint;
var
    num:array[-1..15] of longint;
    i,len,flag:longint;
    s:string;
begin
    fillchar(num,sizeof(num),0); Delete(s,1,length(s));
    str(ans,s); len:=length(s); flag:=1;
    for i:=1 to len do val(s[i],num[len-i+1]);
    for i:=len downto 1 do
    begin
        dec(ans,num[i]*fuck[i-1,2]);
        if flag=666 then dec(ans,num[i]*fuck[i-1,1]) else
        begin
            if num[i]>4 then dec(ans,fuck[i-1,1]);
            if num[i]>6 then dec(ans,fuck[i-2,1]);
            if (num[i+1]=6)and(num[i]>2) then dec(ans,fuck[i-1,1]);
        end;
        if (num[i]=4)or((num[i+1]=6)and(num[i]=2)) then flag:=666;
    end;
    exit(ans);
end;

begin
    fuck[0,1]:=1;
    for i:=1 to 9 do
    begin
        fuck[i,1]:=fuck[i-1,1]*9-fuck[i-2,1];
        fuck[i,2]:=fuck[i-1,2]*10+fuck[i-1,1]+fuck[i-2,1];
    end;
    read(x,y);
    while (x<>0)and(y<>0) do
    begin
        writeln(Slove(y+1)-Slove(x)); read(x,y);
    end;
end.

$$II$$

同样是递推来做。

我们设 \(f_{i,j}\)\((j-1) \times 10^{i-1}+1\)~\(j \times 10^{i-1}\) 的幸运数字的个数。跟平常一样,是为了压空间,也是为了好转移。

转移就异常的简单了:(且满足 \(x\ y\) 不是 \(6\ 2\),也不是 \(4\))

\[f_{i,x}=\sum\limits^{9}_{x=0} \sum\limits^{9}_{y=0} f_{i-1,y} \]

然后我们在查询的时候直接加上就好了。

来谈谈一直没有说过的查询,其实随便列举一个数字 \(2333\):

  • 最高位 : \(0\)$1000$,$1001$\(2000\)
  • 次高位 : \(0\)$100$,$101$\(200\),\(201\)~\(300\)
  • 第三高位 : \(0\)$10$,$11$\(20\),\(21\)~\(30\)
  • 最低位 : \(0\)$1$,$1$\(2\),\(2\)~\(3\)

所以大概明白了数位 \(DP\) 的套路。

var
	fuck:array[-1..15,-1..10] of int64;
	i,x,y:longint;

function Slove(x:longint):int64;
var
	num:array[-1..15] of longint;
	i,j,len:longint;
	s:string;
begin
    fillchar(num,sizeof(num),0);
	str(x,s); len:=length(s); Slove:=0;
	for i:=1 to len do val(s[i],num[len-i+1]);
	for i:=len downto 1 do
	begin
		for j:=0 to num[i]-1 do
		begin
			if (num[i+1]=6)and(j=2) then continue;
			inc(Slove,fuck[i,j]);
		end;
		if (num[i]=4) then break;
        if (num[i]=2)and(num[i+1]=6) then break;
	end;
end;

begin
	for i:=0 to 9 do fuck[1,i]:=1; fuck[1,4]:=0;
	for i:=2 to 10 do for x:=0 to 9 do for y:=0 to 9 do 
	begin 
		if (x=6)and(y=2) then continue; 
		if (x=4)or(y=4) then continue; 
		inc(fuck[i,x],fuck[i-1,y]); 
	end;
	read(x,y);
	while (x<>0)and(y<>0) do
	begin
		writeln(Slove(y+1)-Slove(x));
        read(x,y);
	end;
end.

第一道数位 \(DP\)

由于空间不够,所以我们必须要压缩一下。

所以我们设 \(f_{i,j}\)\(1\)~\(X\)\(windy\)。(其中 \(X\) 满足第一位等于 \(j\),且有 \(i\) 位)

\[f_{i,j}=\sum\limits^{9}_{l=j+2} f_{i-1,l} +\sum\limits^{j-2}_{l=0} f_{i-1,l} \]

for i:=0 to 9 do fuck[1,i]:=1;
	for i:=2 to 20 do for j:=0 to 9 do 
		begin 
			for k:=j-2 downto 0 do inc(fuck[i,j],fuck[i-1,k]); 
			for k:=j+2 to 9 do inc(fuck[i,j],fuck[i-1,k]); 
		end;

这样子就很好的压缩了空间。

我们把 \(C(l,r)\) 变为 \(C(r+1)-C(l)\)

(\(C(r)-C(l-1)\) 莫名 \(WA\) 了?)

我们考虑求一个数字 \(x\)\(windy\)。我们定义 \(X\)\(x\) 倒立过来的数组,\(len\) 为数组长度。如 \(x=7192\),\(X_i=\{2,9,1,7\},len=4\)

我们先把 \(0\)~\(999\)\(windy\) 求一下。

\[\sum\limits^{len-1}_{i=1}\sum\limits^{9}_{j=1} f_{i,j} \]

for i:=1 to len-1 do for j:=1 to 9 do inc(Windy,fuck[i,j]);

然后把 \(1000\)~\(6999\) 的求了。

\[\sum\limits^{X_{len}-1}_{i=1} f_{len,i} \]

for i:=1 to num[len]-1 do inc(Windy,fuck[len,i]);

最后的最难搞~要把剩下的求了。

\[\sum\limits^{1}_{i=len-1} \sum\limits^{X_{i}-1}_{j=0} f_{i,j} (abs(j-X_{i+1})>=2) \]

for i:=len-1 downto 1 do
    begin
        for j:=0 to num[i]-1 do 
        	if abs(j-num[i+1])>=2 then inc(Windy,fuck[i,j]);
        if abs(num[i]-num[i+1])<2 then break;
    end;

就没了。

posted @ 2019-01-25 21:44  Rattry  阅读(88)  评论(0)    收藏  举报