【学习笔记】数位 dp
数位 dp
点击查看目录
前置知识:
-
五大基础 dp
-
前导 \(0\):任何一个数的最高位往上的数位都是 \(0\)。
概念:
数位 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\) 的计数,这样的过程产生的计数答案可以放入一个通用的数组,对于这样的数组我们设计转移状态,进行动态规划。
有时也会用到一些计数技巧:
对于统计答案,往往采用记忆化搜索或是递推。
就伴着第一道例题讲实现吧:
[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}\):
(\(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\),那么你在这一位取的值一定不能大于求解数字该位上的值。
有转移方程:
而本题我们使用记忆化搜索,它有一个很重要的点:
贴上界的状态不能记忆化,因为第一位贴了上界,第二位就可能继续贴上界,如果记忆化则不会继续搜索
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
,否则可能出现转换十六进制是乱码的情况。 -
记得初始化变量或清空容器。(如果
linux
和windows
系统跑的都与评测机不同,看看你是不是在使用一个变量时忘了清空) -
记忆化搜索不需要
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;
}