请注意:一定要看到最后!
原题链接:
HDU
题意简述
定义一个关于数位的函数
f
(
a
,
b
)
=
f(a,b)=
f(a,b)=
{
1
(
当
a
在
b
进
制
下
不
是
回
文
数
)
b
(
别
的
情
况
)
\begin{cases} 1 \qquad(当a在b进制下不是回文数)\\ b \qquad(别的情况) \end{cases}
{1(当a在b进制下不是回文数)b(别的情况)
求
∑
b
a
s
e
=
l
r
∑
j
=
L
R
f
(
j
,
b
a
s
e
)
\sum\limits_{base=l}^{r}\sum\limits_{j=L}^{R}f(j,base)
base=l∑rj=L∑Rf(j,base)
(偷偷改了一下。。。)
数据
输入
多组数据。先有一个 T T T表示 T T T组数据,然后是 T T T行每行四个正整数 L , R , l , r L,R,l,r L,R,l,r,表示式子中的参数 L , R , l , r L,R,l,r L,R,l,r。 1 < = l < = r < = 36 , 1 < = L < = R < = 1 0 9 1<=l<=r<=\red{36},1<=L<=R<=10^9 1<=l<=r<=36,1<=L<=R<=109。
输出
T
T
T行。每行有
1
1
1个正整数,表示第
T
T
T组数据的答案。输出格式:
C
a
s
e
(
空
格
)
#
i
:
(
空
格
)
a
n
s
(
换
行
)
Case(空格)\#i:(空格)ans(换行)
Case(空格)#i:(空格)ans(换行)
"Case #%d: %d\n"
其中 i i i表示第 i i i组数据, a n s ans ans是第 i i i组数据的答案。
样例
输入
3
1 1 2 36
1 982180 10 10
496690841 524639270 5 20
输出
Case #1: 665
Case #2: 1000000
Case #3: 447525746
思路
在这里顶一下fuzhihong巨佬的这篇博客,写的十分详细,我是靠这个看懂的!
(恕我直言,别的博客都写的啥玩意。。。说要用数位 D P DP DP,珂是没有说具体咋 D P DP DP,我这个蒟蒻,显然看不懂啊。。。)
这个题。。首先我在写题面的时候将原来的式子作了变形,也就是调换了一下 ∑ \sum ∑的位置。
其次,注意到
l
,
r
l,r
l,r的范围很小,所以
l
,
r
l,r
l,r这一层珂以暴力枚举。当然,不同的进制之间几乎没有规律,所以也没有办法优化掉这一层。
回到原问题。如果我们当前枚举到了
b
a
s
e
base
base进制,我们知道这个进制里
L
,
R
L,R
L,R间有
c
n
t
cnt
cnt个回文数,那么后面的和就是
c
n
t
∗
b
a
s
e
+
(
R
−
L
+
1
−
c
n
t
)
cnt*base+(R-L+1-cnt)
cnt∗base+(R−L+1−cnt)。
其中
c
n
t
∗
b
a
s
e
cnt*base
cnt∗base是所有回文数给出的贡献,
(
R
−
L
+
1
−
c
n
t
)
(R-L+1-cnt)
(R−L+1−cnt)是所有不回文的数给出的贡献。
L
,
R
L,R
L,R之间的回文数个数,珂以用
[
1
,
R
]
[1,R]
[1,R]之间减去
[
1
,
L
−
1
]
[1,L-1]
[1,L−1]之间。现在的问题就是:
求 b a s e base base进制下 [ 1 , x ] [1,x] [1,x]之间有多少回文
这就非常毒瘤了。看起来,珂以。。。数位 D P DP DP?
仿佛是的,那就试试看⑧。先写出了这样的函数:
void DFS(bool lim,int base,int start,int pos)
{
}
b a s e base base(表示进制), p o s pos pos(表示当前位置),还有 l i m lim lim(是否选择了 x x x的前缀)是本题中显然需要的元素。这两个不确定,就相当于啥都不确定。接下来我们会发现,前导 0 0 0是不参与回文计算的。也就是说, 00100 00100 00100这样的不是回文数。所以我们也要记录我们是从哪里开始的,用 s t a r t start start表示。
那么,如何保证我们算出来的是回文呢?显然是不能一位一位判的,用fuzhihong巨佬的话说,就玩完了。所以我们要一边搜一边判,显然需要记录我们选了的位是哪些。接下来分三种情况转移:
- 选择了一位
0
0
0(即出现了前导
0
0
0):
注意前导 0 0 0是不参与回文计算的。所以 s t a r t start start要 − 1 -1 −1。当然我们当前考虑的位 p o s pos pos也要 − 1 -1 −1,然后继续去寻找答案。要注意 l i m lim lim的限制。 - 当前的位置超过了对称中心(即
(
s
t
a
r
t
+
1
)
/
2
(start+1)/2
(start+1)/2)
此时就要去和前面选过的位进行比较了。此时由于超过了对称中心,就需要和前面作比较。如果当前枚举的新位 i i i和前面的位不对称,那么就要去找不回文的答案了。那么,不回文有答案么?显然是没有的。比如说,我们枚举到了一个 113 1 2 ∗ ∗ 113\red{1}2** 11312∗∗,显然,后面有啥都不珂能回文了。所以就直接返回 0 0 0好了。由此珂见,我们需要多传一个参数进来,叫 i s _ p a l i n is\_palin is_palin,表示当前是否回文。 - 别的情况
记录好 n o w now now数组, p o s − 1 pos-1 pos−1(即到下一个位置),继续搜索。
当然,我们是记忆化搜索,所以要知道啥时候能用记忆化的结果。如果没有限制条件,而且 d p dp dp(即记忆化数组)上面有值了,就直接返回 d p dp dp中记录的值。 而且也不能忘了每次算完记录一下。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define int long long
int d[60];int cnt=0;
void decompose(int base,int x)
{
cnt=0;
while(x)
{
d[cnt++]=x%base;
x/=base;
}
d[cnt]=0;
}
int dp[60][60][60][2];
int now[60];
int DFS(bool lim,bool is_palin,int base,int start,int pos)
{
if (!is_palin) return 0;//直接退出
if (pos<0)
{
return 1;//此时is_palin是1
//所以肯定有一个解(长度为0是一种解)
}
if (!lim and ~dp[start][pos][base][is_palin])
{
return dp[start][pos][base][is_palin];
}//记忆化
int ans=0;
int up=lim?d[pos]:base-1;//注意限制
for(int i=0;i<=up;++i)
{
now[pos]=i;//记录选择的数组
if (start==pos and i==0)
{
ans+=DFS(lim and i==up,is_palin,base,start-1,pos-1);
}
else if ((start-1)/2>=pos and is_palin)
{
ans+=DFS(lim and i==up,now[start-pos]==i,base,start,pos-1);
}
else
{
ans+=DFS(lim and i==up,is_palin,base,start,pos-1);
}//三种情况
}
if (!lim)
{
dp[start][pos][base][is_palin]=ans;
}
return ans;
}
int calc(int base,int x)
{
memset(now,-1,sizeof(now));
decompose(base,x);
return DFS(1,1,base,cnt-1,cnt-1);
}
int l,r,L,R;
void Solve(int Case)
{
int ans=0;
for(int base=l;base<=r;++base)
{
int cnt=calc(base,R)-calc(base,L-1);
ans+=cnt*base+(R-L+1-cnt);
}
printf("Case #%lld: %lld\n",Case,ans);
}
void IsMyWife()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
int t;scanf("%lld",&t);
memset(dp,-1,sizeof(dp));
for(int Case=1;Case<=t;++Case)
{
scanf("%lld%lld%lld%lld",&L,&R,&l,&r);
Solve(Case);
}
}
#undef int //long long
};
main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}
你以为这就结束了?不!
还有一个瞎搞过题法:打表后二分答案。
我们会发现,确定一个回文数,只需要确定一半,另一半直接 拼 \red{拼} 拼出来。比如在十进制中,我们枚举 123 123 123,就珂以确定两个回文数 12321 12321 12321和 123321 123321 123321。所以,这就告诉我们,在 1 e 9 1e9 1e9以内,回文数的个数大概就是开个根左右(位数枚举一半)。。。即空间是 1 e 5 ∗ 36 ∗ 一 些 常 数 1e5*36*一些常数 1e5∗36∗一些常数。经过我的多次试验,这个常数 = 1.5 =1.5 =1.5就过了。
给出这个代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define int long long
int palin[37][300100];
//每个数会确定两个,所以这里开三倍
int pcnt[37];
int d[70];
void Add1(int base,int x)//第一种情况,即中间重合一部分的情况,长度为奇数
{
int cnt=0;memset(d,0,sizeof(d));
while(x)
{
d[++cnt]=x%base;
x/=base;
}reverse(d+1,d+cnt+1);
for(int i=cnt+1;i<=2*cnt-1;++i)
{
d[i]=d[2*cnt-i];
}
int newx=0;
for(int i=1;i<=2*cnt-1;++i)
{
newx=newx*base+d[i];
}
palin[base][++pcnt[base]]=newx;
}
void Add2(int base,int x)//第二种情况,直接拼接,长度为偶数
{
int cnt=0;
while(x)
{
d[++cnt]=x%base;
x/=base;
}reverse(d+1,d+cnt+1);
for(int i=cnt+1;i<=2*cnt;++i)
{
d[i]=d[2*cnt-i+1];
}
int newx=0;
for(int i=1;i<=2*cnt;++i)
{
newx=newx*base+d[i];
}
palin[base][++pcnt[base]]=newx;
}
void Init()
{
memset(pcnt,0,sizeof(pcnt));
for(int base=2;base<=36;++base)
{
for(int i=1;i<=150000;++i)//1.5倍常数
{
Add1(base,i);
Add2(base,i);//两种情况
}
sort(palin[base]+1,palin[base]+pcnt[base]+1);//方便upper_bound
}
}
int calc(int base,int x)
{
int pos=upper_bound(palin[base]+1,palin[base]+pcnt[base]+1,x)-1-palin[base];
return pos;
}//直接找
int l,r,L,R;
void Soviet()
{
int ans=0;
for(int base=l;base<=r;++base)
{
int cnt=calc(base,R)-calc(base,L-1);
ans+=cnt*base+(R-L+1-cnt);
}
printf("%lld\n",ans);
}
void IsMyWife()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
Init();
int t;scanf("%lld",&t);
for(int i=1;i<=t;++i)
{
scanf("%lld%lld%lld%lld",&L,&R,&l,&r);
printf("Case #%lld: ",i);
Soviet();
}
}
#undef int //long long
};
int main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}
本文探讨了一个关于数位的函数f(a,b),并利用数位DP技术求解特定范围内所有回文数的求和问题。通过详细解析算法思路,包括记忆化搜索、递归转移和二分查找等技巧,展示了如何高效地解决复杂数学问题。
9294

被折叠的 条评论
为什么被折叠?



