「JSOI2016」无界单词
题目描述
对于一个单词 $S$ ,如果存在一个长度 $l$,满足 $0\lt l\lt |S|$,并且使得 $S$ 长度为 $l$ 的前缀与 $S$ 长度为 $l$ 的后缀相同,JYY 则称 $S$ 是有界的。比如 `aabaa` 和 `ababab` 就都是有界的字符串。如果一个单词不存在这样的 $l$ ,则 JYY 称之为无界单词。
现在考虑所有仅由字母 `a` 和 `b` 组成的长度为 $N$ 的字符串,JYY想知道:
1. 一共有多少个无界单词?
2. 这些无界单词中,按字典序排列第 $K$ 小的单词是哪一个?
数据范围
对于全部数据,满足 $1\le T\le 5,1\le N\le 64$ ,并且保证对于任意测试数据,总存在第 $K$ 小的无界单词。
题解
考虑到每个有界单词总是能表示成两个无界单词放在首位且长度不超过该有界单词的一半。
于是可以考虑 $dp$ , $f_i$ 表示长度为 $i$ 的无界单词的个数,转移考虑容斥即可。
第二问只需要按位考虑即可,将 $dp$ 状态表示成长度为 $i$ ,且前 $len$ 为已经确定的无界单词的个数,转移是类似的。
代码
#include <bits/stdc++.h> using namespace std; #define U unsigned long long const int N=70; const U B=793999; int T,n,a[N]; U k,f[N],b[N],g[N]; U H(int l,int r){ return g[r]-g[l-1]*b[r-l+1]; } bool J(int x){ for (int i=1;i<x;i++) if (H(1,i)==H(x-i+1,x)) return 0; return 1; } void work(){ cin>>n>>k; f[1]=2; for (int i=2;i<=n;i++) f[i]=0; for (int i=2;i<=n;i++){ for (int j=1;j+j<=i;j++) f[i]+=f[j]*((U)1<<(i-j-j)); f[i]=((U)1<<i)-f[i]; } if (n==64) f[n]--; cout<<f[n]<<endl; for (int h=1;h<=n;h++){ g[h]=g[h-1]*B+1;a[h]=0; for (int i=1;i<=n;i++) f[i]=0; for (int i=1;i<=h;i++) if (J(i)) f[i]=1; for (int i=h+1;i<=n;i++){ for (int j=1;j+j<=i;j++){ if (i-j+1<=h){ if (H(1,h-i+j)==H(i-j+1,h)) f[i]+=f[j]; } else f[i]+=f[j]*((U)1<<(i-j-max(j,h))); } f[i]=((U)1<<(i-h))-f[i]; } if (f[n]<k) a[h]++,k-=f[n]; putchar(a[h]+97); g[h]=g[h-1]*B+a[h]+1; } putchar('\n'); } int main(){ b[0]=1; for (int i=1;i<N;i++) b[i]=b[i-1]*B; for (scanf("%d",&T);T--;work()); return 0; }