【学习笔记】数位 dp

数位 dp

点击查看目录

前置知识:

  • 记忆化搜索

  • 五大基础 dp

  • 前导 \(0\):任何一个数的最高位往上的数位都是 \(0\)

oi-wiki

洛谷题单(自编)

概念:

数位 dp,就是一个用来计数的动态规划,将一个长数分解为一个一个数位上的数进行统计。

例如:求 \(a-b\) 区间不包含 \(c\) 的数的个数,保证 \(0\leq a\leq b\leq 2\times 10^9\)

空间限制 256MiB,时间限制 1000ms。

这个范围一眼暴力 TLE,等死。

直接动态规划记录数字?MLE 等着你。

所以有个东西叫数位 dp,它一般应用于:

  • 求给定区间 \([a,b]\) 之内的符合条件的数的个数,即结果是计数,有左右边界。

  • 条件与数的大小无关,与各数位上的数有关。、

  • 上界较大,如(\(10^{18}\))。

实现:

从左边界 \(l\) 数到右边界 \(r\),过程中拥有非常多的重复部分,如:\(l=309\) 数到 \(r=831\),之中有非常相似的过程:从 \(400\) 数到 \(499\),从 \(500\) 数到 \(599\),诸如此类后两位从 \(00\) 变为 \(99\) 的计数,这样的过程产生的计数答案可以放入一个通用的数组,对于这样的数组我们设计转移状态,进行动态规划。

有时也会用到一些计数技巧:

\[ \begin{aligned} ans_{l,r}=ans_{0,r}-ans_{0,l-1} \end{aligned} \]

对于统计答案,往往采用记忆化搜索或是递推。

就伴着第一道例题讲实现吧:

[ZJOI2010] 数字计数

题目链接

折叠题干

[ZJOI2010] 数字计数

题目描述

给定两个正整数 \(a\)\(b\),求在 \([a,b]\) 中的所有整数中,每个数码(digit)各出现了多少次。

输入格式

仅包含一行两个整数 \(a,b\),含义如上所述。

输出格式

包含一行十个整数,分别表示 \(0\sim 9\)\([a,b]\) 中出现了多少次。

样例 #1

样例输入 #1

1 99

样例输出 #1

9 20 20 20 20 20 20 20 20 20

提示

数据规模与约定

  • 对于 \(30\%\) 的数据,保证 \(a\le b\le10^6\)
  • 对于 \(100\%\) 的数据,保证 \(1\le a\le b\le 10^{12}\)

求给出的边界 \([a,b]\) 中,每个数码(\(0-9\))出现了多少次。

对于满 \(i\) 位(指第 \(i\) 位可以从 \(0\) 枚举到 \(9\))的数,所有数字出现次数相同。

解题:

\(f_i\) 是满 \(i\) 位的数每个数字出现的次数,有 \(1-i\) 位的贡献=\(1-(i-1)\)位置的贡献\(\times 10+10^{i-1}\)

\[ \begin{aligned} f_i=10\times f_{i-1}+10^{i-1} \end{aligned} \]

(\(10^{i-1}\) 是因为在第 \(i\) 位置产生贡献的情况下忽略第 \(i\) 位置,\(1-(i-1)\) 位置从全是 \(0\) 枚举到全是 \(9\)\(10^{i-1}\) 的贡献由第 \(i\) 位产生)

考虑统计答案,将上界按位分开,从高到低枚举防漏,\(a_i\) 表示在第 \(i\) 位置的数,分着考虑:

  • \(1-(i-1)\) 位置的贡献,为 \(f_{i-1}\times a_i\)

  • \(i\) 位置的数不是 \(a_i\) 时,不管后面什么数。贡献为 \(fac_{i-1}.\)\(10\) 的阶乘)

  • \(i\) 位置上的数是 \(a_i\) 时,其贡献是后面的数加上 \(1\) (后面的数全是 \(0\) 也行)

  • 前导 \(0\)。第 \(i\) 位是前道 \(0\) 时,第 \(1-(i-1)\) 位都是 \(0\),减去重复计数答案。

如果还不太明白就看代码注释,自己拿一个数字推一下,我就不推了:

(由于这道题明显递推更加简单所以选择递推)

Miku'sCode
#include<bits/stdc++.h>
using namespace std;

typedef long double llf;
typedef long long intx;
const int maxn=15;

int a[maxn]; 
intx l,r,f[maxn],fac[maxn];
//fac是10的阶乘,数据小于1e12故到13 
intx ans1[maxn],ans2[maxn];

void input(){
	scanf("%lld %lld",&l,&r);
}

void pre(){
//预处理 
	fac[0]=1;
	for(int i=1;i<=13;++i){
		f[i]=f[i-1]*10+fac[i-1];
		fac[i]=(intx)10*fac[i-1];
		cout<<"###"<<i<<' '<<f[i]<<endl;
	}
}

void work(intx n,intx *ans){
//求1-n所有数的数码计数和,放入ans数组 
	intx tmp=n;
	int len=0;
	while(n)	a[++len]=n%10,n=n/10;
	for(int i=len;i>=1;--i){
	//从高位向低位模拟 
		for(int j=0;j<=9;++j)	ans[j]=ans[j]+f[i-1]*a[i];
		//不贴近上界,随便取值
		/*
		简单来说:1-(i-1)位置从0000到9999的某个数的计数是f[i-1]
		到a[i]算是贴近上界,这里加的是 1-(i-1)位置的数 
		*/ 
		for(int j=0;j<a[i];++j)	ans[j]=ans[j]+fac[i-1];
		//贴近上界,取0到上界
		/*
		简单来说
		就是把在第i位从0到上界-1的数的贡献加起来了。 
		*/ 
		tmp=tmp-fac[i-1]*a[i]; 
		ans[a[i]]=ans[a[i]]+tmp+1;
		//将最后的上界上的数贡献加起来
		ans[0]=ans[0]-fac[i-1];
		//若第i位置是前导0,减去重复计数 
	}
}

int main(){
	pre();
	input();
	work(r,ans1);
	work(l-1,ans2);
	for(int i=0;i<=9;++i){
	//计数原理[1-r]-[1-(l-1)]=[l-r] 
		printf("%lld ",(intx)ans1[i]-ans2[i]);
	}
	return 0;
}


杂题乱写

Windy数

过了上面那个模板我们上题。

题目链接

折叠题干

[SCOI2009] windy 数

题目背景

windy 定义了一种 windy 数。

题目描述

不含前导零且相邻两个数字之差至少为 \(2\) 的正整数被称为 windy 数。windy 想知道,在 \(a\)\(b\) 之间,包括 \(a\)\(b\) ,总共有多少个 windy 数?

输入格式

输入只有一行两个整数,分别表示 \(a\)\(b\)

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

1 10

样例输出 #1

9

样例 #2

样例输入 #2

25 50

样例输出 #2

20

数据规模与约定

对于全部的测试点,保证 \(1 \leq a \leq b \leq 2 \times 10^9\)

解题:

就是对于一个给定的区间 \([l,r]\) 求各个相邻的数位上的数相差至少为 \(2\) 的个数。(前导 \(0\) 不算)

还是用我们的计数公式先把他转化一下:\(ans_{l,r}=ans_{1,r}-ans_{1,l-1}\)

因为我们的当前位能否取到 \(9\) 是与上一位是否取到最高数相关的,举个例子:

对于从高位向低位取数:\(20070831\)

对于 \(200708▇▇\),我们第二位只能取到 \(3\)

对于 \(200707▇▇\),我们第二位可以取到 \(9\)

所以我们必须要记录上一位的数。

因此可以定义 \(f_{i,j,[0/1]}\) 表示从高到低走到第 \(i\) 位,上一位是 \(j\) 时的答案,而 \([0/1]\) 表示是否等于上一位,如果是 \(1\),那么你在这一位取的值一定不能大于求解数字该位上的值。

有转移方程:

\[ \begin{aligned} f_{i,j,op}=\sum_{k=1}^{maxx}\limits f_{i+1,k,k==maxx} \end{aligned} \]

而本题我们使用记忆化搜索,它有一个很重要的点:

贴上界的状态不能记忆化,因为第一位贴了上界,第二位就可能继续贴上界,如果记忆化则不会继续搜索

Miku's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rg register int
#define il inline
namespace mystd{
	il int Max(int a,int b){
		if(a<b)	return b;
		else	return a;
	}
	il int Min(int a,int b){
		if(a>b)	return b;
		else	return a;
	}
	il int Abs(int a){
		if(a<0)	return a*(-1);
		else	return a;
	}
}

const int maxn=15;

int l,r,ansl,ansr;
int f[maxn][maxn];
vector<int> num;

il void input(){
	scanf("%lld %lld",&l,&r);
}

int dfs(int pos,int st,int op){
	if(!pos)	return 1;
	if(!op && ~f[pos][st])	return f[pos][st];
	//-1取反为0,这里指f[x][st]!=-1,记忆化
	int maxx; 
	if(op==0)	maxx=9;
	else	maxx=num[pos];
//	cout<<"$$$"<<maxx<<endl;
	int res=0;
	for(int i=0;i<=maxx;++i){
		if(mystd::Abs(st-i)<2)	continue;
		//判断条件
		if(st==11 && i==0)	res=res+dfs(pos-1,11,op & (i==maxx));
		else	res=res+dfs(pos-1,i,op & (i==maxx)); 
		//op&(i==maxx) 表示以当前位向前所有位都与求解数字相同则为1
	}
	if(!op)	f[pos][st]=res;
	//注意贴上界的状态不能记忆化,因为第一位贴了上界,第二位就可能继续贴上界,如果记忆化则不会继续搜索 
	return res;
}

int solve(int x){
	for(int i=0;i<=maxn-1;++i){
		for(int j=0;j<=maxn-1;++j){
			f[i][j]=-1;
		}
	}
	num.clear();
	num.push_back(-1);
	//去除前导0 
	int t=x;
	while(x){
		num.push_back(x%10);
		x=x/10;
	}
	int siz=num.size()-1;
	return dfs(siz,11,1);
} 

signed main(){
	input();
	ansl=solve(l-1);
	ansr=solve(r);
	int ans=ansr-ansl;
	printf("%lld",ans);
	return 0;
}

XOR Triangle

题目链接

折叠题干

XOR Triangle

题面翻译

题目描述
给你一个数 \(n\),问:有多少对数 \(0\leq a,b,c \leq n\) 满足 \(a \oplus b,b \oplus c,a \oplus c\) 。三个数字构成了一个非退化三角形,也就是两条短边之和大于第三边的长度。\(\oplus\) 表示二进制下的异或操作。

输入格式
一个数字 \(n\),表示给定的 n 在二进制下的表示。

输出格式
输出答案 mod 998244353。

题目描述

You are given a positive integer $ n $ . Since $ n $ may be very large, you are given its binary representation.

You should compute the number of triples $ (a,b,c) $ with $ 0 \leq a,b,c \leq n $ such that $ a \oplus b $ , $ b \oplus c $ , and $ a \oplus c $ are the sides of a non-degenerate triangle.

Here, $ \oplus $ denotes the bitwise XOR operation.

You should output the answer modulo $ 998,244,353 $ .

Three positive values $ x $ , $ y $ , and $ z $ are the sides of a non-degenerate triangle if and only if $ x+y>z $ , $ x+z>y $ , and $ y+z>x $ .

输入格式

The first and only line contains the binary representation of an integer $ n $ ( $ 0 < n < 2^{200,000} $ ) without leading zeros.

For example, the string 10 is the binary representation of the number $ 2 $ , while the string 1010 represents the number $ 10 $ .

输出格式

Print one integer — the number of triples $ (a,b,c) $ satisfying the conditions described in the statement modulo $ 998,244,353 $ .

样例 #1

样例输入 #1

101

样例输出 #1

12

样例 #2

样例输入 #2

1110

样例输出 #2

780

样例 #3

样例输入 #3

11011111101010010

样例输出 #3

141427753

提示

In the first test case, $ 101_2=5 $ .

  • The triple $ (a, b, c) = (0, 3, 5) $ is valid because $ (a\oplus b, b\oplus c, c\oplus a) = (3, 6, 5) $ are the sides of a non-degenerate triangle.
  • The triple $ (a, b, c) = (1, 2, 4) $ is valid because $ (a\oplus b, b\oplus c, c\oplus a) = (3, 6, 5) $ are the sides of a non-degenerate triangle.

The $ 6 $ permutations of each of these two triples are all the valid triples, thus the answer is $ 12 $ .

In the third test case, $ 11,011,111,101,010,010_2=114,514 $ . The full answer (before taking the modulo) is $ 1,466,408,118,808,164 $ .

解题:

概况一下题意:

对于区间 \([0,n]\) 有多少对 \(k_1=a\oplus b\)\(k_2=b\oplus c\)\(k_3=a\oplus c\)\(k_1,k_2,k_3\) 可以构成三角形。

(对于相同的 \(k\) 与不同的 \(a,b,c\),看做不同的 \(k\)

比较容易发现的是 \(k_1\oplus k_2\oplus k_3=0\)

所以考虑什么时候 \(k_1,k_2,k_3\) 能组成三角形。

发现不会,正难则反,考虑什么时候不能构成三角形。

\(k_1+k_2=k_3\) 时,不能组成三角形,而着意味着 \(k_1\oplus k_2=k_1+k_2\),即 \(k_1\) 在二进制下没有和 \(k_2\) 相同的位,\(k_1\text{&}k_2=0\)

否则 \(k_3=k_1\oplus k_2\) 就一定小于 \(k_1+k_2\),构成三角形,所以这是充分必要条件。

于是考虑数位 dp,三个 \(lim\) 变量表示是否贴上界,三个 \(mj\) 变量表示是否 \(k_1\oplus k_2=1,k_2\oplus k_3=1,k_1\oplus k_3=1\)

转移比较平凡,枚举即可。

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define MYMAX 0x3f3f3f3f
#define cout std::cout
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
const ff eps=1e-8;
typedef __int128_t INT;
typedef std::pair<int,int> PII;
typedef std::vector<int> VI;
typedef std::set<int> SI;

int Max(int x,int y)    <% return x<y?y:x; %>
int Min(int x,int y)    <% return x<y?x:y; %>
int Abs(int x)  <% return x>0?x:-x; %>
#if ONLINE_JUDGE
char INN[1<<20],*p1=INN,*p2=INN;
#define getchar() (p1==p2 && (p2=(p1=INN)+fread(INN,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#endif
il int read(){
    char c=getchar();
    int x=0,f=1;
    while(c<48) <% if(c=='-')f=-1;c=getchar(); %>
    while(c>47) x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=2e5+5,mod=998244353;


char s[maxn+5];
int n,f[maxn][2][2][2][2][2][2];

ll dfs(int pos,bool lim1,bool lim2,bool lim3,bool mj1,bool mj2,bool mj3){
    if(pos==n+1){
        if(mj1==true && mj2==true && mj3==true) return 1;
        return 0;
    }
    if(~f[pos][lim1][lim2][lim3][mj1][mj2][mj3])    return f[pos][lim1][lim2][lim3][mj1][mj2][mj3];
    ll res=0;
    int lim=s[pos]-'0';
    for(rg i=0;i<=1;++i){
        if(lim1==true && i>lim)  continue;
        for(rg j=0;j<=1;++j){
            if(lim2==true && j>lim)  continue;
            for(rg k=0;k<=1;++k){
                if(lim3==true && k>lim) continue;
                int k1=i^j,k2=j^k,k3=i^k;
                res=(res+dfs(pos+1,(lim1==true && i==lim),(lim2==true && j==lim),(lim3==true && k==lim),(mj1|(k1&&k2)),(mj2|(k2&&k3)),(mj3|(k1&&k3))))%mod;
            }
        }
    }
    f[pos][lim1][lim2][lim3][mj1][mj2][mj3]=res;
    return res;
}

int main(){
#ifndef ONLINE_JUDGE
freopen("xor.in","r",stdin);
#endif
    scanf("%s",s+1);
    n=strlen(s+1);
    for(rg S=1;S<=n+1;++S)
        for(rg O=0;O<=1;++O)
            for(rg N=0;N<=1;++N)
                for(rg _=0;_<=1;++_)
                    for(rg E=0;E<=1;++E)
                        for(rg T=0;T<=1;++T)
                            for(rg Y=0;Y<=1;++Y)
                                f[S][O][N][_][E][T][Y]=-1;
    printf("%lld\n",dfs(1,1,1,1,0,0,0));
    return 0;
}

[ABC317F] Nim

题目链接

折叠题干

[ABC317F] Nim

题面翻译

给定四个正整数 \(N,A_1,A_2,A_3\),试求满足一下条件的三元组 \(\left(X_1,X_2,X_3 \right)\) 的个数,对 \(998244353\) 取模。

  • \(1 \le X_i \le N,i=1,2,3\)
  • \(A_i \mid X_i\)\(i=1,2,3\)
  • \(\left(X_1 \bigoplus X_2 \right) \bigoplus X_3=0\)

题目描述

整数 $ N,A_1,A_2,A_3 $ が与えられます。以下の $ 3 $ つの条件を全て満たすような正整数の組 $ (X_1,X_2,X_3) $ の個数を $ 998244353 $ で割ったあまりを求めてください。

  • 全ての $ i $ で $ 1\leq\ X_i\ \leq\ N $ である。
  • 全ての $ i $ で $ X_i $ は $ A_i $ の倍数である。
  • $ (X_1\ \oplus\ X_2)\ \oplus\ X_3\ =\ 0 $ である。ただし、$ \oplus $ はビット単位の xor を表す。

ビット単位 xor とは非負整数 $ A,\ B $ のビット単位 xor 、$ A\ \oplus\ B $ は、以下のように定義されます。 - $ A\ \oplus\ B $ を二進表記した際の $ 2^k $ ($ k\ \geq\ 0 \() の位の数は、\) A,\ B $ を二進表記した際の $ 2^k $ の位の数のうち一方のみが $ 1 $ であれば $ 1 $、そうでなければ $ 0 $ である。

例えば、$ 3\ \oplus\ 5\ =\ 6 $ となります (二進表記すると: $ 011\ \oplus\ 101\ =\ 110 $)。

输入格式

入力は以下の形式で標準入力から与えられる。

$ N $ $ A_1 $ $ A_2 $ $ A_3 $

输出格式

答えを出力せよ。

样例 #1

样例输入 #1

13 2 3 5

样例输出 #1

4

样例 #2

样例输入 #2

1000000000000000000 1 1 1

样例输出 #2

426724011

样例 #3

样例输入 #3

31415926535897932 3 8 4

样例输出 #3

759934997

提示

制約

  • $ 1\ \leq\ N\ \leq\ 10^{18} $
  • $ 1\ \leq\ A_i\ \leq\ 10 $
  • 入力は全て整数である

Sample Explanation 1

$ (X_1,X_2,X_3) $ が $ (6,3,5),(6,12,10),(12,6,10),(12,9,5) $ のときの $ 4 $ 通りが条件を満たします。

解题:

(哈哈,你爹 10 维数位dp来咯)

亦或可以想二进制,所以数位 dp 填二进制数,和上一道题承接是非常好的,刚好可以练手,所以就简单写写。

对于亦或和为 \(0\),这一位只有四种可能:\(000\)\(110\)\(011\)\(101\)

能填这些数的就直接转移过去,然后就得到答案了。

Miku's Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
#define rg register int
typedef long double llf;
typedef long long ll;
typedef pair<int,int> PII;
const double eps=1e-8;
namespace io{
	#if ONLINE_JUDGE
	char in[1<<20],*p1=in,*p2=in;
	#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,1<<20,stdin),p1==p2)?EOF:*p1++)
	#endif
	il ll read(){
   		char c=getchar();
    	ll x=0,f=1;
   		while(c<48)<%if(c=='-')f=-1;c=getchar();%>
    	while(c>47)x=(x*10)+(c^48),c=getchar();
    	return x*f;
	}
	il void write(int x){
    	if(x<0)<%putchar('-');x=~x+1;%>
    	if(x>9) write(x/10);
   		putchar(x%10+'0');
	}
	il int ins(char *str){
	 	int len=0;
	  	while(1){
			char c=getchar();
			if(c!='\n' && c!='\0' && c!='\r')	str[++len]=c;
			else{
		    	break;
			}
	  	}
		return len;
	}
}

using namespace io;
int N,a1,a2,a3;
ll f[63][20][20][20][2][2][2][2][2][2];
vector<int> num;

il void pre(){
	N=read(),a1=read(),a2=read(),a3=read();
	while(N)	<% num.push_back(N&1);N=N>>1; %>
		for(rg A=0;A<=num.size();++A)
		for(rg P=0;P<=19;++P)
			for(rg j=0;j<=19;++j)
				for(rg F=0;F<=19;++F)
					for(rg e=0;e<=1;++e)
						for(rg n=0;n<=1;++n)
							for(rg g=0;g<=1;++g)
								for(rg c=0;c<=1;++c)
									for(rg _=0;_<=1;++_)
										for(rg __=0;__<=1;++__)
											f[A][P][j][F][e][n][g][c][_][__]=-1;
} 

ll dfs(int pos,int x,int y,int z,bool flag1,bool flag2,bool flag3,bool mj1,bool mj2,bool mj3){
//pos是当前进行至二进制第几位,x,y,z是当前数%a1,a2,a3,flag1,2,3是当前位是否贴上界,mj1,2,3是其二进制位上的数是否为1
	if(pos==-1){
		if(x==0 && y==0 && z==0 && mj1==true && mj2==true && mj3==true)	return 1;
		else	return 0;
	}
	if(~f[pos][x][y][z][flag1][flag2][flag3][mj1][mj2][mj3])	return f[pos][x][y][z][flag1][flag2][flag3][mj1][mj2][mj3]; 
	int res=dfs(pos-1,(x<<1)%a1,(y<<1)%a2,(z<<1)%a3,(flag1==true && (num[pos]==0)),(flag2==true && (num[pos]==0)),(flag3==true && (num[pos]==0)),mj1,mj2,mj3)%mod;
//	//新的一位上的二进制数数全取0 
	if((flag1==false || num[pos]==1) && (flag2==false || num[pos]==1))	res=(res+dfs(pos-1,(x<<1|1)%a1,(y<<1|1)%a2,(z<<1)%a3,flag1,flag2,(flag3==true && (num[pos]==0)),1,1,mj3))%mod;
	if((flag1==false || num[pos]==1) && (flag3==false || num[pos]==1))	res=(res+dfs(pos-1,(x<<1|1)%a1,(y<<1)%a2,(z<<1|1)%a3,flag1,(flag2==true && (num[pos]==0)),flag3,1,mj2,1))%mod;
	if((flag2==false || num[pos]==1) && (flag3==false || num[pos]==1))	res=(res+dfs(pos-1,(x<<1)%a1,(y<<1|1)%a2,(z<<1|1)%a3,(flag1==true && (num[pos]==0)),flag2,flag3,mj1,1,1))%mod;
	return f[pos][x][y][z][flag1][flag2][flag3][mj1][mj2][mj3]=res;
}

signed main(){
	pre();
	printf("%lld\n",dfs(num.size()-1,0,0,0,1,1,1,0,0,0));
	return 0;
}

独特的数字

折叠题干

独特的数字

内存限制:512 MiB 时间限制:3000 ms 标准输入输出

题目类型:传统 评测方式:文本比较

题目描述

小X称一个任意进制的数字是独特的,当且仅当在该进制下每一个数位上的数字都不同。
小X对十进制和十六进制数很感兴趣,现在他对你提出了两种问题:

  • \([l, r]\) 中有多少个数是独特的。
  • \(0\) 开始第 \(l\) 个独特的数是多少。

小X当然知道啦,但是他想考考你...

输入格式

第一行一个正整数 \(T\) ,表示小X的问题个数。
接下来 \(T\) 行,每行首先一个字母’d’或’h’,’d’代表这个问题是在十进制下的,’h’代表这个问题是在十六进制下的。之后一个数字 \(op\),\(op = 0\) 表示是第一个问题,\(op = 1\) 表示是第二个问题。对于第一个问题,后面两个整数 \(l\),\(r\),对于第二个问题,后面一个正整数 \(l\)。注意在十六进制下输入的 \(l\),\(r\) 也是十六进制数。

输出格式

\(T\) 行,对于第一个问题,输出独特的数的个数(用对应进制输出)。对于第二个问题,输出第 \(l\) 个独特的数(用对应进制输出),如果不存在输出’-’。

样例

样例输入:

6
d 0 10 20
h 0 10 1f
d 1 10
h 1 f
d 1 1000000000
h 1 ffffffffffffffff

样例输出:

10
f
9
e
-
-

数据范围与提示

对于70%数据,只有’d’操作,其中:

• 对于10%数据,T ≤ 5, l, r ≤ 106,只有0操作。

• 对于30%数据,l, r ≤ 106,只有0操作。

• 对于50%数据,l, r ≤ 109,只有0操作。

对于100%数据,T ≤ 50000, 0 ≤ l ≤ r < 264。

解题:

很明显的数位dp。(就是有些难调)

思路就是对于操作 \(0\) 就求 \(ans_r-ans{l-1}\),操作二就二分答案。

十进制与十六进制就多一个转换操作。

简单说一下要注意什么:

  • 数据范围需要unsigned long long,标识符为%llu

  • 某些位置不能使用 unsigned long long,因为有 \(-1\),应该用 long long,否则可能出现转换十六进制是乱码的情况。

  • 记得初始化变量或清空容器。(如果linuxwindows系统跑的都与评测机不同,看看你是不是在使用一个变量时忘了清空)

  • 记忆化搜索不需要memset清空。

  • 位运算比等号优先级低,记得加括号。

  • 十进制与十六进制的转化记得考虑 \(0\) 的情况并单独输出,十六进制记得要判断左端点是 \(0\) 单独为答案加 \(1\)

  • 动态规划表示从 \(0\) 开始的方案数。

  • 二分答案要判断右端点有没有动,没动输出-

Miku's Code
#include<bits/stdc++.h>
using namespace std;
#define Miku long long
#define rg register Miku
#define il inline
#define int unsigned long long
typedef long double llf;
const double eps=1e-8;
namespace mystd{
	il int Max(int a,int b)<%if(a<b) return b;return a; %>
	il int Min(int a,int b)<%if(a>b) return b;return a; %>
	il int Abs(int a)<% if(a<0) return a*(-1);return a; %>
	il double fMax(double a,double b)<%if(a<b) return b;return a; %>
	il double fMin(double a,double b)<%if(a>b) return b;return a; %>
	il double fAbs(double a)<% if(a<0) return a*(-1);return a; %>
	il int dcmp(double a){
		if(a<-eps)	return -1;
		if(a>eps)	return 1;
		return 0;
	}
}
const int maxn=70;

int T,op,l,r,x;

char L[maxn],R[maxn],X[maxn];
int f1[maxn][(1<<10)+1];
int f2[maxn][(1<<16)+1];
char opt;
vector<int>num;

int qpow(int x,int y){
//打表用 
	int ans=1;
	while(y){
		if(y&1)	ans=ans*x;
		x=x*x;
		y=y>>1;
	}
	return ans;
}

il void pre(){
	for(int i=0;i<=maxn-1;++i){
		for(int j=0;j<=(1<<16);++j){
			f2[i][j]=-1;
		}
	}
	for(int i=0;i<=maxn-1;++i){
		for(int j=0;j<=(1<<10);++j){
			f1[i][j]=-1;
		}
	}
}

int dfs1(Miku pos,int s,int op,int mj){
//pos是当前位,s是状态,op是是否贴上界,mj是是否去除前导零 
	if(pos==-1)	return 1;
	if(!op && ~f1[pos][s])	return f1[pos][s];
	int maxx=0;
	if(op==0)	maxx=9;
	else	maxx=num[pos];
	int res=0;
	for(rg i=0;i<=maxx;++i){
		if((s&(1<<i))!=0)	<% continue; %>
		if(mj==1 && i==0)	res=res+dfs1(pos-1,s,op&(i==maxx),1);
		else	res=res+dfs1(pos-1,s|(1<<i),op&(i==maxx),0);
	}
	if(!op)	f1[pos][s]=res;
	return res;
}

int solve1(int x){
	num.clear();
	while(x){
		num.push_back(x%10);
		x=x/10;
	}
	Miku siz=num.size()-1;
	return dfs1(siz,0,1,1);
}

int dfs2(Miku pos,int s,int op,int mj){
	if(pos==-1)	return 1;
	if(!op && ~f2[pos][s])	return f2[pos][s];
	int maxx=0;
	if(op==0)	maxx=15;
	else	maxx=num[pos];
	int res=0;
	for(rg i=0;i<=maxx;++i){
		if((s&(1<<i))!=0)	continue;
		if(mj==1 && i==0)	res=res+dfs2(pos-1,s,op&(i==maxx),1);
		else	res=res+dfs2(pos-1,s|(1<<i),op&(i==maxx),0);
	}
	if(!op)	f2[pos][s]=res;
	return res;
}

int solve2(char *x){
	num.clear();
	int slen=strlen(x+1);
	for(rg i=slen;i>=1;--i){
		if('0'<=x[i] && x[i]<='9')	num.push_back(x[i]-'0');
		else	num.push_back(x[i]-'a'+10); 
	}
	rg siz=num.size()-1;
	return dfs2(siz,0,1,1);
}

il void print(int x){
	num.clear();
	if(x==0)	<% putchar('0');putchar('\n');return; %>
	while(x){
		num.push_back(x%16);
		x=x/16;
	}
	rg siz=num.size()-1;
	for(rg i=siz;i>=0;--i){
		if(0<=num[i] && num[i]<=9)	printf("%llu",num[i]);
		else{
			char ch='a'+(num[i]-10);
			putchar(ch);
		}
	}
	putchar('\n');
}
int fac[16]={1,16,256,4096,65536,1048576,16777216,268435456,4294967296,68719476736,1099511627776,17592186044416,281474976710656,4503599627370496,72057594037927936,1152921504606846976};
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("mine.txt","w",stdout); 
	pre();
	scanf("%llu",&T);
	while(T--){
		cin>>opt;
		scanf("%llu",&op);
		if(op==0 && opt=='d'){
			int ansl=0,ansr=0;
			scanf("%llu %llu",&l,&r);
			if(l==0)	ansl=0;
			else	ansl+=solve1(l-1);
			ansr+=solve1(r);
//			printf("%llu %llu\n",ansl,ansr);
			printf("%llu\n",ansr-ansl);
		}
		else if(op==0 && opt=='h'){
			int ansl=0,ansr=0;
			scanf("%s %s",L+1,R+1);
			int len=strlen(L+1);
			if(len!=1 || L[1]!='0'){
				for(int i=len;i>=1;--i){
					if(L[i]!='0'){
						L[i]=L[i]-1;
						break;
					}
					else{
						L[i]='f';
					}
				}
			}
			else{
				ansr+=1;
			}
			ansl+=solve2(L),ansr+=solve2(R);
			print(ansr-ansl);
		}
		else if(op==1 && opt=='d'){
			scanf("%llu",&x);
			int l=0,r=1e11,ans=0;
			while(l<=r){
				int mid=(l+r)>>1;
				if(solve1(mid)>=x)	ans=mid,r=mid-1;
				else	l=mid+1;
			}
			if(ans==0)	putchar('-'),putchar('\n');
			else printf("%llu\n",ans);
		}
		else if(op==1 && opt=='h'){
			scanf("%s",X+1);
			int l=0,r=18364758544493064722ull,ans=0;
			int len=strlen(X+1);
			num.clear();
			for(rg i=len;i>=1;--i){
				if('0'<=X[i] && X[i]<='9')	num.push_back(X[i]-'0');
				else	num.push_back(X[i]-'a'+10);	
			}
			int siz=num.size()-1;
			x=0;
			for(rg i=0;i<=siz;++i){ 
				x=x+fac[i]*num[i];
			}
//			printf("###%llu\n",x);
			while(l+1<r){
				int mid=0;
				if(((l&1)==1) && ((r&1)==1))	mid=(l>>1)+(r>>1)+1;
				else	mid=(l>>1)+(r>>1);
				int save=mid;
				num.clear();
				while(save){
					num.push_back(save%16);
					save=save/16;
				}
				Miku siz=num.size()-1;
				if(dfs2(siz,0,1,1)>=x)	r=mid;
				else	l=mid;
			}
//			printf("###%llu\n",l);
			if(r==18364758544493064722ull)	putchar('-'),putchar('\n');
			else print((int)l+1ull);
		}
	}
} 

[ABC295F] substr = S

题目链接

折叠题干

[ABC295F] substr = S

题面翻译

\(T\) 组数据。

每组数据你会得到一个字符串 \(S\) 和两个整数 \(L,R\)

我们定义 \(f(i)\) 表示 \(i\) 的十进制表示中有几个连续子串恰好等于 \(S\)

\(\sum_{i=L}^R f(i)\)

Translated by Tx_Lcy

题目描述

$ T $ 個のテストケースについて、数字のみからなる文字列 $ S $ と正整数 $ L,R $ が与えられるので、以下の問題を解いてください。

正整数 $ x $ に対して $ f(x)= $ ( $ x $ を ( 先頭に $ 0 $ を含まないように ) 書き下した文字列の連続部分列のうち $ S $ と合致するものの個数 ) と定義します。

例えば $ S= $ 22 であるとき、$ f(122)\ =\ 1,\ f(123)\ =\ 0,\ f(226)\ =\ 1,\ f(222)\ =\ 2 $ となります。

このとき、 $ \displaystyle\ \sum_{k=L}^{R}\ f(k) $ を求めてください。

输入格式

入力は以下の形式で標準入力から与えられる。$ \rm{case}_i $ は $ i $ 個目のテストケースを表す。

$ T $ $ \rm{case}{1} $ $ \rm{case} $ $ \vdots $ $ \rm{case}_{\it{T}} $

各テストケースは以下の形式である。

$ S $ $ L $ $ R $

输出格式

全体で $ T $ 行出力せよ。
そのうち $ i $ 行目には $ i $ 番目のテストケースに対する答えを整数として出力せよ。

样例 #1

样例输入 #1

6
22 23 234
0295 295 295
0 1 9999999999999999
2718 998244353 9982443530000000
869120 1234567890123456 2345678901234567
2023032520230325 1 9999999999999999

样例输出 #1

12
0
14888888888888889
12982260572545
10987664021
1

提示

制約

  • $ 1\ \le\ T\ \le\ 1000 $
  • $ S $ は数字のみからなる長さ $ 1 $ 以上 $ 16 $ 以下の文字列
  • $ L,R $ は $ 1\ \le\ L\ \le\ R\ <\ 10^{16} $ を満たす整数

Sample Explanation 1

この入力には $ 6 $ 個のテストケースが含まれます。 - $ 1 $ つ目のケースは $ S= $ 22 $ ,L=23,R=234 $ です。 - $ f(122)=f(220)=f(221)=f(223)=f(224)=\dots=f(229)=1 $ - $ f(222)=2 $ - 以上より、このケースに対する答えは $ 12 $ です。 - $ 2 $ つ目のケースは $ S= $ 0295 $ ,L=295,R=295 $ です。 - $ f(295)=0 $ となることに注意してください。

解题:

发现这道题还挺有意思的。

其实思路还是挺好想的,你可以枚举这个串 \(S\) 第一次出现的位置为 \(st\),所以它的末尾就是 \(to=st+len\)

然后二分方案数,看看对于这个方案数,它的 \(x\) 值是多少,是否超过 \(l\)(或 \(r\)),然后就得到了 \(ans_l\)\(ans_r\)

注意,因为 \(S\) 是串,而 \(l\)\(r\) 却是数,所以我们不得不选择从右往左的数法,但是在我的定义中,\(st\) 仍然在 \(to\) 的左边,不过 \(st\) 的枚举范围为 \(len\to 16\),而 \(to=st-len\)

然后发现需要特判 \(s_0\) 是不是 \(0\)

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define MYMAX 0x3f3f3f3f
#define cout std::cout
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
const ff eps=1e-8;
typedef __int128_t INT;
typedef std::pair<int,int> PII;
typedef std::vector<int> VI;
typedef std::set<int> SI;
#define int long long
int Max(int x,int y)    <% return x<y?y:x; %>
int Min(int x,int y)    <% return x<y?x:y; %>
int Abs(int x)  <% return x>0?x:-x; %>
// #if ONLINE_JUDGE
// char INN[1<<30],*p1=INN,*p2=INN;
// #define getchar() (p1==p2 && (p2=(p1=INN)+fread(INN,1,1<<30,stdin),p1==p2)?EOF:*p1++)
// #endif
il int read(){
    char c=getchar();
    int x=0,f=1;
    while(c<48) <% if(c=='-')f=-1;c=getchar(); %>
    while(c>47) x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=30;

int T,len;
ll l,r,pw[maxn],snum,ansl;
char s[maxn];

ll qpow(int x,int k){
//x^k
    ll res=1;
    while(k){
        if(k&1) res=res*x;
        x=x*x;
        k=k>>1;
    }
    return res;
}

il void clear(){
    for(rg i=1;i<=len;++i)  s[i]='\0';
    snum=0;
}

ll check(ll mid,ll st){
// 二分st~to位置有mid种放置贡献,返回x需要的值
    int to=st-len;
    if(s[1]=='0')   mid+=pw[to];
    return mid/pw[to]*pw[st]+snum*pw[to]+mid%pw[to];
}

ll solve(ll x){
    ll res=0;
    for(rg i=len;i<=16;++i){// 枚举s第一位的位置
        if(check(0,i)>x) continue;
        ll L=1,R=pw[16-len],ans=0;
        while(L<=R){
            ll mid=(L+R)>>1;
            if(check(mid-1,i)<=x)  ans=mid,L=mid+1;
            else    R=mid-1;
        }
        res+=ans;
    }
    return res;
}

il void input(){
    scanf("%s",s+1);
    len=strlen(s+1);
    for(rg i=1;i<=len;++i)  snum+=(s[i]-'0')*pw[len-i];
    // cout<<"snum="<<snum<<"; len="<<len<<endl;
    l=read(),r=read();
}

signed main(){
#ifndef ONLINE_JUDGE
freopen("sub.in","r",stdin);
#endif 
    pw[0]=1;
    for(rg i=1;i<=16;++i)   pw[i]=pw[i-1]*10;
    T=read();
    while(T--){
        input();
        ansl=solve(r)-solve(l-1);
        printf("%lld\n",ansl);
        clear();
    }
    return 0;
}

[JSOI2016] 位运算

题目链接

折叠题干

[JSOI2016] 位运算

题目描述

JYY 最近在研究位运算。他发现位运算中最有趣的就是异或 (xor) 运算。对于两个数的异或运算,JYY 发现了一个结论:两个数的异或值为 \(0\) 当且仅当他们相等。于是 JYY 又开始思考,对于 \(N\) 个数的异或值会有什么性质呢?

JYY 想知道,如果在 \(0\)\(R-1\) 的范围内,选出 \(N\) 个不同的整数,并使得这 \(N\) 个整数的异或值为 \(0\),那么一共有多少种选择的方法呢?(选择的不同次序并不作重复统计,请参见样例)

JYY 是一个计算机科学家,所以他脑海里的 \(R\) 非常非常大。为了能够方便的表达,如果我们将 \(R\) 写成一个 \(01\) 串,那么 \(R\) 是由一个较短的 \(01\)\(S\) 重复 \(K\) 次得到的。比如,若 \(S=101\)\(K=2\),那么 \(R\) 的二进制表示则为 \(101101\)。由于计算的结果会非常大,JYY 只需要你告诉他选择的总数对 \(10^9+7\) 取模的结果即可。

输入格式

第一行包含两个正整数 \(N\)\(K\)

接下来一行包含一个由 \(0\)\(1\) 组成的字符串 \(S\)

我们保证 \(S\) 的第一个字符一定为 \(1\)

输出格式

一行一个整数,表示选择的方案数对 \(10^9+7\) 取模的值。

样例 #1

样例输入 #1

3 1
100

样例输出 #1

1

提示

样例说明

唯一的一种选择方法是选择 \(\{1,2,3\}\)


数据范围

对于 \(100\%\) 的数据,\(3 \le N \le 7\)\(1 \le k \le 10^5\)\(1 \le |S| \le 50\)

给定两个整数 \(n,k\) 与一个 \(01\)\(S\),而 \(R\) 就是 \(S\) 重复 \(K\) 次,求 \(0-(R-1)\) 的范围内,有 \(n\) 个数异或和为 \(0\),求有多少选择方案。

我们发现 \(R\) 给我们的时候就已经是 \(01\) 串的形式,就别改了,配合异或,我们就直接按二进制思考。

设我们选出的数是 \(x_1,x_2,\dots,x_n\),而保证 \(n\) 个数互不相同且 \(\in[1,R-1]\),我们设 \(R>x_1>x_2>x_3>\dots>x_n\)

因为 \(n\leq 7\),所以考虑状态压缩,设 \(f_{i,s}\) 表示当前到了 \(R\) 的从左往右第 \(i\) 位,而 \(s\) 是一个二进制数,表示选的 \(n\) 个数的状态,对于 \(s\) 的第 \(j\) 位置,如果对于 \(1-i\)\(x_j=x_{j-1}\),则设 \(s\) 的第 \(j\) 位是 \(1\),否则为 \(0\)

最后我们需要 \(s\)\(1\) 的数量为偶数,便能保证其异或和为 \(0\)

而转移只需要枚举 \(x_1,x_2,\dots,x_n\) 的第 \(i\) 位填什么数然后转移即可,而这个填什么数我们可以再次装压成 \(s2\),时间复杂度为 \(O(k|S|\cdot 2^{2n}n)\)

然而我们的 \(R\) 是由 \(k\)\(S\) 拼接而来,所以我们的 dp 是一个重复的过程,可以矩阵优化。

\(trans_{st,to}\) 表示 \(f_{x|s|,st}\) 转移到 \(f_{(x+1)|s|,to}\) 的转移系数 \(\left(0\leq x\leq \left(k-1\right)\right)\),于是进行如上的 dp 得到系数,然后快速幂,最后乘上即可。

时间复杂度:\(O(|S|\cdot 2^{3n}n+2^{3n}logk)\)

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define int long long
#define MYMAX 0x3f3f3f3f
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double ff;
typedef long double llf;
const ff eps=1e-8;
typedef __int128_t INT;

int Max(int x,int y)    <% return x<y?y:x; %>
int Min(int x,int y)    <% return x<y?x:y; %>
int Abs(int x)  <% return x>0?x:-x; %>
il int read(){
    char c=getchar();
    int x=0,f=1;
    while(c<48) <% if(c=='-')f=-1;c=getchar(); %>
    while(c>47) x=(x*10)+(c^48),c=getchar();
    return x*f;
}const int maxn=10,maxk=1e5+5,maxs=55,mod=1e9+7;

int n,k,len,lim,bitcnt[1<<7],cnt;
char s[maxs];
int f[maxs][1<<7];
struct matrix{
    int a[1<<7][1<<7],siz;
    matrix(){
        for(rg i=0;i<=(1<<7)-1;++i)    for(rg j=0;j<=(1<<7)-1;++j)    a[i][j]=0;
        // memset(a,0,sizeof(a));
        siz=0;
    }
    matrix operator*(const matrix &mm)const{
        matrix res;
        res.siz=siz;
        for(rg i=0;i<=siz;++i)
            for(rg j=0;j<=siz;++j)
                for(rg k=0;k<=siz;++k)
                    res.a[i][j]=(res.a[i][j]+1ll*a[i][k]*mm.a[k][j]%mod)%mod;
        return res;
    }
};matrix trans,ans;

matrix mat_qpow(matrix X,int k){
// X^k
    matrix res;
    res.siz=X.siz;
    for(rg i=0;i<=X.siz;++i)    res.a[i][i]=1;
    while(k){
        if(k&1) res=res*X;
        X=X*X;
        k=k>>1;
    }
    // cerr<<"###"<<endl;
    return res;
}

il void clear(){
    for(rg i=0;i<=len+1;++i)  for(rg j=0;j<=(1<<n);++j)  f[i][j]=0;
}

il void work(){
// 处理一小重复段
    trans.siz=lim;
    for(rg st=0;st<=lim;++st){// 枚举状态s,希望得到st->to的转移系数
        clear();
        f[0][st]=1;
        for(rg i=1;i<=len;++i){// 枚举前i位
            for(rg s1=0;s1<=lim;++s1){// 枚举状态s1
                if(f[i-1][s1]){
                    for(rg s2=0;s2<=lim;++s2){// 枚举状态s2,表示新填入的数
                        if(!(bitcnt[s2]&1)){// 这一二进制位不能有奇数个1,否则异或和一定不为0
                            int bits[maxn];
                            bits[0]=(s[i]-'0');
                            for(rg j=1;j<=n;++j)    bits[j]=((s2>>(j-1))&1);
                            // bits[j]第j位上填的新数
                            bool mj=false;
                            int news=0;
                            for(rg j=1;j<=n;++j){
                                if((s1>>(j-1))&1){  //s1第j位上的数是1表示num[j]=num[j-1]
                                    if(bits[j]>bits[j-1]){ mj=true;break; } // 假设了num[j]<num[j-1]
                                    if(bits[j]==bits[j-1])  news=news|(1<<(j-1));
                                }
                            }
                            if(mj==true)    continue;
                            f[i][news]=(f[i][news]+f[i-1][s1])%mod;
                        }
                    }
                }
            }
        }
        for(rg to=0;to<=lim;++to)   trans.a[st][to]=f[len][to];
    }
}

il void input(){
    n=read(),k=read();
    scanf("%s",s+1);
    len=strlen(s+1);
    lim=(1<<n)-1;
    for(rg i=1;i<=lim;++i)  bitcnt[i]=bitcnt[i>>1]+(i&1); 
}

signed main(){
#ifndef ONLINE_JUDGE
freopen("ttt.in","r",stdin);
#endif
    input();
    work();
    trans=mat_qpow(trans,k);
    ans.a[0][lim]=1;
    ans.siz=lim;
    ans=ans*trans;
    printf("%lld\n",ans.a[0][0]);
    return 0;
}
posted @ 2023-08-01 21:12  Sonnety  阅读(55)  评论(6编辑  收藏  举报