「アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python の 3 つの言語で提供されており、トピックは主に中レベルから低レベルのトピックであり、初級レベルから上級レベルの学生に適しています。
「 レインボーナンバー 」、リンク: http://oj.ecustacm.cn/problem.php?id=1840
質問の説明
【タイトル説明】レインボーナンバー:先頭に0のない10進整数で、隣り合う2つの数字は異なります。
下限と上限を指定して、それらの間にある虹の数を数えます。
[入力形式]入力の最初の行は、下限を表す正の整数 L です。2 行目は正の整数 R で、上限を表します。
1≤L≤R≤10^(100000)。ここでの数値の長さは最大 100000 であることに注意してください。
【出力形式】答えを表す数値を出力します 答えが大きすぎるため、998244353の余りが必要です。
【入力サンプル】
样例1:
1
10
样例2:
12345
65432
【出力サンプル】
样例1:
10
样例2:
35882
答え
区間[L,R]の範囲が非常に広いため、各数値が虹の数値であるかどうかを直接暴力的に検証するとタイムアウトになってしまいます。また、数値が大きすぎるため、数値で直接計算するのは不便です。大きな数値を高精度に処理し、配列 num[] を使用して大きな数値を格納することができます。num[i] は i 番目の桁です。多数の。
この質問はデジタル統計に関連しており、デジタル統計に関する比較的直接的な DP の質問です。このコードは、「アルゴリズム コンペティション」の「5.3.2 デジタル統計 DP のメモリ検索実装」の dfs() テンプレートを使用します。[デジタル統計 DP の原理と 2 つのコーディング方法については、「アルゴリズム コンペティション」、清華大学出版局、羅を参照してください。ヨンジュン、郭偉斌著、333~338ページ。この質問のコードは、書籍のテンプレートを適用します。] で、dfs() で数を数えるときに、虹の数を除外するだけです。
dp[] を、先頭にゼロと無制限の桁制限を備えたレインボー数値の数として定義します。たとえば、
dp[1]、01 ~ 09 内のレインボー数値の数、dp[1] = 9 と定義します。
dp[2]、001~099の範囲内の虹の番号の数、dp[2] = 81。虹の数字 001、002、...、009、011、022、...099 を除く合計 18 が除外され、dp[2] = 99-18 = 81 となります。
dp[3]、0001 ~ 0999 内の虹の番号の数。dp[3] = 729 に対応します。ただし、虹の番号 0001、0002、0099、0100、…0111、0112、…、0999 は除きます。
コードステップ:
(1) L、R を読み取ります。28 行目では、L と R を文字列として読み込みます。
(2)solve(s) は範囲 [1, s] 内の虹の数を計算し、[L, R] 内の虹の数はsolve®-solve(L-1) です。L は文字列形式で表現された数値なので、29 ~ 32 行目で L-1 の文字列形式を事前に計算します。
(3)solve(s) では、まず文字列 s で表される大きな数値を配列 num[] に変換し、その大きな数値を num[1]~num[len] に格納します。たとえば、s = "65432" と入力すると、num[1]~num[5] =2、3、4、5、6 が得られます。
(4) dfs() は [1, s] 内のレインボー番号の数をカウントします。s は num[1] ~ num[len] で表される大きな数です。13 行目では先頭のゼロを除いた数字の数が累積され、14 行目では虹の数字が削除されます。
複雑さについては以下で説明します。dfs() の主なタスクは dp[] の計算、つまり dp[1] ~ dp[len] を求めることであり、計算量は 10×len 程度と非常に少ないです。
【ポイント】デジタル統計DP。
C++ コード
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int num[100005]; //存输入的数字
ll dp[100005];
ll MOD = 998244353;
ll dfs(int pos,int last,bool lead,bool limit){
//last: 上一位
ll ans = 0;
if(pos==0) return 1; //递归到0位数,结束返回
if(!lead && !limit && dp[pos]!=-1) return dp[pos]; //记忆化搜索
int up=(limit ? num[pos] : 9); //这一位的最大值
for(int i=0;i<=up;i++){
if(i==0 && lead) ans += dfs(pos-1,i,true, limit&&(i==up)); //lead=true,无前导0
else if(last != i) ans += dfs(pos-1,i,false,limit&&(i==up)); //与上一位不同,排除彩虹数
ans %= MOD;
}
if(!lead && !limit) dp[pos] = ans; //状态记录:有前导0,无数位限制
return ans;
}
ll solve(string s){
int len=0;
for(int i=s.length()-1;i>=0;i--) //把字符串转成数字,存入num[1]~num[len]
num[++len]=(s[i]-'0'); //例如输入“65432”,得num[1:5]=2 3 4 5 6
memset(dp,-1,sizeof(dp));
return dfs(len,-1,true,true);
}
int main(){
string L,R; cin>>L>>R;
for(int i=L.length()-1;i>=0;i--){
//求L-1,例如L=12345,L-1=12344
if(L[i]!='0') {
L[i]--; break;} //这一位不是0,减1即可
else L[i]='9'; //这一位是0,减1得9
}
cout<<((solve(R)-solve(L))%MOD + MOD) % MOD;
return 0;
}
Javaコード
import java.util.*;
public class Main {
static long[] dp = new long[100005];
static int[] num = new int[100005];
static long MOD = 998244353;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String L = scanner.next();
String R = scanner.next();
StringBuilder resultL = new StringBuilder(L);
for (int i = L.length() - 1; i >= 0; i--) {
if (resultL.charAt(i) != '0') {
resultL.setCharAt(i, (char)(resultL.charAt(i) - 1));
break;
} else {
resultL.setCharAt(i, '9');
}
}
System.out.println((solve(R) - solve(resultL.toString()) + MOD) % MOD);
}
public static long dfs(int pos, int last, boolean lead, boolean limit) {
long ans = 0;
if (pos == 0) return 1;
if (!lead && !limit && dp[pos] != -1) return dp[pos];
int up = (limit ? num[pos] : 9);
for (int i = 0; i <= up; i++) {
if (i == 0 && lead) ans += dfs(pos - 1, i, true, limit && (i == up));
else if (last != i) ans += dfs(pos - 1, i, false, limit && (i == up));
ans %= MOD;
}
if (!lead && !limit) dp[pos] = ans;
return ans;
}
public static long solve(String s) {
int len = 0;
num = new int[100005];
for (int i = s.length() - 1; i >= 0; i--)
num[++len] = s.charAt(i) - '0';
Arrays.fill(dp, -1);
return dfs(len, -1, true, true);
}
}
Pythonコード
solve(s) では、まず文字列 s で表される大きな数値を配列 num[] に変換し、その大きな数値を num[0] ~ num[len-1] に格納します。たとえば、s = "65432" と入力すると、num[0]~num[4] =2、3、4、5、6 が得られます。
import sys
sys.setrecursionlimit(100000)
MOD = 998244353
dp = [-1] * 100005
num = []
def dfs(pos, last, lead, limit):
if pos == -1: return 1
global dp
global num
if not lead and not limit and dp[pos] != -1: return dp[pos]
up = num[pos] if limit else 9
ans = 0
for i in range(up + 1):
if i == 0 and lead: ans += dfs(pos - 1, i, True, limit and (i == up))
elif last != i: ans += dfs(pos - 1, i, False, limit and (i == up))
ans %= MOD
if not lead and not limit: dp[pos] = ans
return ans
def solve(s):
global dp
global num
num = [int(c) for c in s[::-1]] #把字符串转成数字,存入num[0]~num[len-1]
dp = [-1] * 100005
return dfs(len(num) - 1, -1, True, True)
L = input()
R = input()
L_minus_one = str(int(L) - 1)
result = (solve(R) - solve(L_minus_one) + MOD) % MOD
print(result)