【题目链接】
http://www.bnuoj.com/v3/problem_show.php?pid=34985
【题目大意】
你有[0,k]k+1个字符,现在你要构造长度为n的字符串。要求任意连续k+1个字符不能是[0,k]的一个排列,问你有多少种构造方法。
【思路】
n很大,所以第一直觉,这题不是DP+矩阵,就是数学(比方容斥)。
如果用dp[i][j]表示长度为i,状态为j,满足题意的方法数。现在我们去思考状态j表示什么?
直接进制压缩?好像太大了。但是注意到,从最后一个字符往前数,如果一个字符在后面出现了。这意味什么?这意味着,如果包括这个重复字符,再也构造不出来非法串(有某个字符出现了2次或以上)。
那么,我们不妨让j表示,从最后一个字符开始,不出现重复字符的最大长度。比方“ ... 3 3 2 3 1 ”对应的状态为j == 3,“... 1 2 3 1”也对应状态j == 3。这个状态的转移是很简单的,如果你放不同于最后j个字符的字符,转移到j + 1。而填最后1个,最后2个...最后j个相同的字符,对应着状态1 - j 各一次。
当然,直接DP,复杂度为O(n*k*k)。我们用矩阵优化一下,复杂度降为O(log(n)*k^3)。
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef long long LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF = 100011122;
const double INFF = 1e100;
const double eps = 1e-8;
const LL mod = 20140518;
const int NN = 11;
const int MM = 5000010;
/* ****************** */
LL xx[NN][NN], ans[NN][NN], temp[NN][NN];
void jz_cf(LL a[][NN],LL b[][NN],LL c[][NN],int n)
{
int i,j,k;
for(i = 1; i <= n; i ++)
for(j = 1; j <= n; j ++)
{
c[i][j] = 0;
for(k = 1; k <= n; k ++)
{
c[i][j] += a[i][k]*b[k][j];
c[i][j] %= mod;
}
}
for(i = 1; i <= n; i ++)
for(j = 1; j <= n; j ++)
a[i][j] = c[i][j];
}
LL solve(LL n,int k)
{
memset(xx, 0, sizeof(xx));
memset(ans, 0, sizeof(ans));
int i, j;
LL sum = 0;
for(i = 1; i <= k; i++)
{
ans[i][i] = 1;
for(j = 1; j <= k + 1; j++)
{
if(j <= i)
xx[j][i]++;
else
xx[i+1][i]++;
}
}
for(n--; n > 0; n>>=1)
{
if(n&1)
jz_cf(ans,xx,temp,k);
jz_cf(xx,xx,temp,k);
}
for(i = 1; i <= k; i ++)
{
sum += ans[i][1]*(k+1);
sum %= mod;
}
return sum;
}
int main()
{
int k, cas, ee = 0;
LL n, ans;
scanf ("%d", &cas);
while (cas--)
{
cin >> n >> k;
ans = solve(n, k);
printf("Case #%d: %lld\n", ++ee, ans);
}
return 0;
}