@description@

用 N 个不同的字符(编号1 - N),组成一个字符串,有如下要求:

(1) 对于编号为 i 的字符,如果2 * i > n,则该字符可以作为结尾字符。如果不作为结尾字符而是中间的字符,则该字符后面可以接任意字符。

(2) 对于编号为 i 的字符,如果2 * i <= n,则该字符不可以作为结尾字符。作为中间字符,那么后面接的字符编号一定要 >= 2 * i。

问有多少长度为M且符合条件的字符串,由于数据很大,只需要输出该数Mod 10^9 + 7的结果。

例如:N = 2,M = 3。则abb, bab, bbb是符合条件的字符串,剩下的均为不符合条件的字符串。

input

第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 20)

第2 - T + 1行:每行2个数,N, M中间用空格分割,N为不同字符的数量,M为字符串的长度。(2 <= N, M <= 10^18)

output

共T行,对应符合条件的字符串的数量。由于数据很大,只需要输出该数Mod 10^9 + 7的结果。

sample input

1

6 3

sample output

73

@solution@

什么鬼题目。。。

@part - 1@

不难写出一个很 naive 的 dp:定义 dp[i][j] 长度为 i,末尾字符为 j 的方案数。

对 2*j > n 还是 <= n 进行分类讨论就可以得到转移式。

不难发现对于 2*j > n 的 dp 值是相同的,我们令 f[i] 表示这个值。

于是我们可以发现 dp[i][j] 总是可以表示为 f[i-1], f[i-2], ... 的线性组合。

考虑这个性质的实际意义:我们可以在一个字符后面添加若干的字符最终变为以 2*x > n 这一类字符结尾。

我们以 2*x > n 这一类字符作为隔断,将最终的字符串分成若干以这一类字符结尾的段。可以发现每一段的长度是 O(log) 的,因为后一个字符至少是前一个字符的两倍。

定义 g[i][x] 表示以字符 x 开头,产生长度 i 的段的方案数。于是根据定义就有 g[1][x] = 1,对于 2*x > n。

同时也有 f[i] = ∑g[j][x]*f[i-j]。因为段的长度不会超过 O(log),所以可以预处理 g 然后矩乘。

@part - 2@

先来考虑矩乘这一部分,顺便把我们上述的内容整理一下。

定义 f[i] 表示问题所想我们求解的答案,再定义 g[i][x] 表示以字符 x 开头,最后一位且只有最后一位是 2*x > n 这类字符,长度为 i 的字符串方案数。

于是有转移 \(f[i] =\sum_{j, k}g[j][k]*f[i-j]\)。

注意到 g[i][x] 这种字符串,每多一位,末尾字符就会变为前一个的字符两倍。故它的长度是 O(log) 的。

所以我们在预处理好 g 之后,可以写一个 O(log^3 n*log m) 的矩乘求出 f[n]。

但是这样有些卡,我们可能需要进一步的优化。

使用一种名为“常系数齐次线性递推”的黑科技即可。(可以点击我的这篇博客查看详情

不过这道题可以直接暴力多项式乘法与多项式取模,因为 log 也不大,说不定 fft 的常数反而会跑得慢些。

最后时间可以优化到 O(log^2 n*log m)

@part - 3@

考虑怎么预处理好 g。不难发现 g 有如下的转移式:

\[g[i][x]=\sum_{2*x\le j}g[i-1][j]
\]

然后开始玄幻起来了。我们使用万能的归纳法,尝试证明 g 始终满足以下形式:

\[g[i][x]=\begin{cases}
P1(x) & 1 \le x \le \lfloor{\frac{a}{2}}\rfloor \\
P2(x) & \lfloor{\frac{a}{2}}\rfloor + 1 \le x \le a
\end{cases}\]

即一个分段的多项式函数。

假设对于所有 <= i 的都成立,尝试证明对 i+1 成立。

如果 \(\lfloor{\frac{a}{2}}\rfloor + 1 \le 2*x \le a\),则:

\[g[i+1][x] = \sum_{2*x\le j}^{j\le a}g[i][j] = \sum_{2*x\le j}^{j\le a}P2(j) = \sum_{j=0}^{a}P2(j) - \sum_{j=0}^{2*x-1}P2(j)
\]

前一项是一个常数,而后一项呢?我们不妨令 \(P2(x) = a_0+a_1x+...\)。

于是:

\[\sum_{j=0}^{2*x-1}P2(j)=a_0(\sum_{j=0}^{2*x-1}j^0)+a_1(\sum_{j=0}^{2*x-1}j^1)+...
\]

然后你发现那是个自然数幂和。然后根据你所了解到的知识,自然数幂和可以表示为多项式形式。然后得证。

如果 \(1 \le 2*x \le \lfloor{\frac{a}{2}}\rfloor\),证明是类似的,不再赘述。

关于自然数幂和,这里有两篇(我认为可供参考的)博客:blog1, blog2

找时间可以专门研究这玩意儿(咕咕咕)。

然后就没有然后了。预处理一下自然数幂和的系数之类的即可。

具体可以看代码。

@accepted code@

#include<cstdio>
typedef long long ll;
const int MOD = int(1E9) + 7;
const int MSIZE = 64;
int pow_mod(int b, int p) {
int ret = 1;
while( p ) {
if( p & 1 ) ret = 1LL*ret*b%MOD;
b = 1LL*b*b%MOD;
p >>= 1;
}
return ret;
}
int len;
struct poly{
int k[2*MSIZE + 5];
poly() {for(int i=0;i<=2*MSIZE;i++) k[i] = 0;}
int get_val(int x) {
int ret = 0;
for(int i=len;i>=0;i--)
ret = (1LL*ret*x%MOD + k[i])%MOD;
return ret;
}
friend poly operator +(poly A, poly B) {
poly C;
for(int i=0;i<MSIZE;i++)
C.k[i] = (A.k[i] + B.k[i])%MOD;
return C;
}
friend poly operator -(poly A, poly B) {
poly C;
for(int i=0;i<MSIZE;i++)
C.k[i] = (A.k[i] + MOD - B.k[i])%MOD;
return C;
}
friend poly operator *(int k, poly B) {
poly C;
for(int i=0;i<MSIZE;i++)
C.k[i] = 1LL*k*B.k[i]%MOD;
return C;
}
friend poly mul_mod(poly A, poly B, poly C) {
poly D;
for(int i=0;i<=2*len;i++)
for(int j=0;j<=i;j++)
D.k[i] = (D.k[i] + 1LL*A.k[j]*B.k[i-j]%MOD)%MOD;
for(int i=2*len;i>=len;i--) {
for(int j=0;j<=len;j++)
D.k[i-len+j] = (D.k[i-len+j] + MOD - 1LL*D.k[i]*C.k[j]%MOD)%MOD;
}
return D;
}
void debug() {
for(int i=0;i<2*MSIZE;i++)
printf("%d ", k[i]);
puts("");
}
}pw[MSIZE + 5], a[MSIZE + 5];
int comb[MSIZE + 5][MSIZE + 5];
void init() {
for(int i=0;i<MSIZE;i++) {
comb[i][0] = 1;
for(int j=1;j<=i;j++)
comb[i][j] = (comb[i-1][j] + comb[i-1][j-1])%MOD;
}
for(int i=0;i<MSIZE;i++) {
for(int j=0;j<=i+1;j++)
pw[i].k[j] = comb[i+1][j];
for(int j=0;j<i;j++)
pw[i] = (pw[i] - comb[i+1][j]*pw[j]);
pw[i] = 1LL*pow_mod(i+1, MOD-2)*pw[i];
}
for(int i=0;i<MSIZE;i++)
for(int j=0;j<=i+1;j++)
for(int k=0;k<=j;k++)
a[i].k[k] = (a[i].k[k] + 1LL*pw[i].k[j]*(1LL*comb[j][k]*(1LL*pow_mod(2, k)*pow_mod(MOD-1, j-k)%MOD)%MOD)%MOD)%MOD;
}
int get_pw(ll x, int k) {return pw[k].get_val(x%MOD);}
int get_pw(ll l, ll r, int k) {return (get_pw(r, k) + MOD - get_pw(l - 1, k))%MOD;}
int get_val(poly A, ll l, ll r) {
int ret = 0;
for(int i=0;i<len;i++)
ret = (ret + 1LL*get_pw(l, r, i)*A.k[i]%MOD)%MOD;
return ret;
}
poly trans(poly A, ll r) {
poly B; B.k[0] = get_val(A, 0, r);
for(int i=0;i<len;i++)
B = B - A.k[i]*a[i];
return B;
}
poly get_M(ll n) {
poly A, B, M; B.k[0] = M.k[len] = 1;
for(int i=0;i<len;i++,n/=2){
int sA = get_val(A, 1, n/2), sB = get_val(B, n/2 + 1, n);
M.k[len-i-1] = (MOD - (sA + sB)%MOD)%MOD;
A = trans(A, n/2), A.k[0] = (A.k[0] + sB)%MOD;
B = trans(B, n);
}
return M;
}
poly p_pow(poly M, ll p) {
poly ret, b; b.k[1] = 1, ret.k[0] = 1;
while( p ) {
if( p & 1 ) ret = mul_mod(ret, b, M);
b = mul_mod(b, b, M);
p >>= 1;
}
return ret;
}
int solve(ll n, ll m) {
ll tmp = n; for(len = 0; tmp; len++, tmp /= 2);
return p_pow(get_M(n), m+len-1).k[len-1];
}
int main() {
init(); int T; scanf("%d", &T);
for(int i=1;i<=T;i++) {
ll n, m; scanf("%lld%lld", &n, &m);
printf("%d\n", solve(n, m));
}
}

@details@

我可能永远也想不到把 g 表示成分段多项式函数的形式这一步。。。

写起来还是比较愉快的吧。。。毕竟没有毒瘤的多项式操作。。。所有多项式操作都可以直接暴力搞。。。

矩乘过不了是真的令人生气。。。

@51nod - 1196/1197/1198@ 字符串的数量的更多相关文章

  1. 51nod1196 字符串的数量

    用N个不同的字符(编号1 - N),组成一个字符串,有如下要求:(1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符.如果不作为结尾字符而是中间的字符,则该字符后面可以接 ...

  2. [51nod1197]字符串的数量 V2

    用N个不同的字符(编号1 - N),组成一个字符串,有如下要求: (1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符.如果不作为结尾字符而是中间的字符,则该字符后面可以 ...

  3. 用map来统计数组中各个字符串的数量

    1.背景 想要统计这一个字符串数组中每一个非重复字符串的数量,使用map来保存其key和value.这个需求在实际开发中经常使用到,我以前总是新建一个空数组来记录不重复字符串,并使用计数器计数,效率低 ...

  4. 51nod 1196 字符串的数量(DP+数论?)

    这题好像是神题...V1 V2 V3分别涵盖了51nod 5级算法题 6级算法题 难题 讨论区的曹鹏神牛好强啊...一种做法切了V1 V2 V3,而且做法是一步一步优化的 还没去看优化的部分,未优化已 ...

  5. 51Nod 1196 字符串的数量

    用N个不同的字符(编号1 - N),组成一个字符串,有如下要求: (1) 对于编号为i的字符,如果2 * i > n,则该字符可以作为结尾字符.如果不作为结尾字符而是中间的字符,则该字符后面可以 ...

  6. 51nod【1196】字符串的数量

    超级神题! 有n种字符,若此种字符的编号( \(1\) ~ \(n\)),\(i*2>n\),则他后面可接任意字符.若不是,则他后面接的字符编号至少要是他的两倍. 问长度为m的字符串的个数. 这 ...

  7. 51nod 1197 字符串的数量 V2(矩阵快速幂+数论?)

    接上一篇,那个递推式显然可以用矩阵快速幂优化...自己随便YY了下就出来了,学了一下怎么用LaTeX画公式,LaTeX真是个好东西!嘿嘿嘿 如上图.(刚画错了一发...已更新 然后就可以过V2了 or ...

  8. 51nod 1092 回文字符串 (dp)

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1092 这个题是poj-3280的简化版,这里只可以增加字符,设 dp[i ...

  9. 51Nod 1003 阶乘后面0的数量(数学,思维题)

    1003 阶乘后面0的数量 基准时间限制:1 秒 空间限制:131072 KB 分值: 5         难度:1级算法题 n的阶乘后面有多少个0? 6的阶乘 = 1*2*3*4*5*6 = 720 ...

随机推荐

  1. idea中查看方法的调用链

    Eclipse的"Call Hierarchy"可以查看一个Java方法或类成员变量的调用树(caller和callee两个方向),非常方便.  在IDEA中类似功能被划分到了三个 ...

  2. 2019-10-18-WPF-解决-StylusPlugIn-点击穿透问题

    title author date CreateTime categories WPF 解决 StylusPlugIn 点击穿透问题 lindexi 2019-10-18 20:55:35 +0800 ...

  3. 洛谷P4145 上帝造题的七分钟2 / 花神游历各国(重题:洛谷SP2713 GSS4 - Can you answer these queries IV)

    题目背景 XLk觉得<上帝造题的七分钟>不太过瘾,于是有了第二部. 题目描述 "第一分钟,X说,要有数列,于是便给定了一个正整数数列. 第二分钟,L说,要能修改,于是便有了对一段 ...

  4. 洛谷P1650 赛马[2017年5月计划 清北学堂51精英班Day1]

    P1650 赛马 题目描述 我国历史上有个著名的故事: 那是在2300年以前.齐国的大将军田忌喜欢赛马.他经常和齐王赛马.他和齐王都有三匹马:常规马,上级马,超级马.一共赛三局,每局的胜者可以从负者这 ...

  5. Leetcode64.Minimum Path Sum最小路径和

    给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入: [   [1,3,1], [1,5,1] ...

  6. IntelliJ IDEA中设置同时打开多个文件且分行显示

    [转载]原文链接:https://blog.csdn.net/lwl20140904/article/details/73275897 有时候要是打开多个文件,要么都显示在一行,要么因为空间不足,就给 ...

  7. Leetcode707.Design Linked List设计链表

    设计链表的实现.您可以选择使用单链表或双链表.单链表中的节点应该具有两个属性:val 和 next.val 是当前节点的值,next 是指向下一个节点的指针/引用.如果要使用双向链表,则还需要一个属性 ...

  8. SQL SERVER 2008 R2 插入数据非常慢

    表是5字段int类型,第一个字段是主健,自增字段 表结构: id int  Uncheckedbillno bigint  Uncheckedopid int  Checkedbillopid int ...

  9. JQuery-- 链式编程、静态函数,自己制作jQuery插件

    一.链式编程 为什么jQuery运行链式编程 ,让我们的代码(方法)连续不间断书写(连续调用)其实主要还是jQuery很多的函数执行完毕之后,都会返回一个jQuery对象 因为获取操作的时候,会返回获 ...

  10. sqlserver 带返回值的存储过程

    create proc test ) output as begin select @result = 'haha' ; end go ), @count int exec @count = test ...