解法
要求第P个,比较好的方法是求在固定前缀下有多个种可能性,然后递归地增加前缀,最后到结尾。
先不考虑固定前缀的问题,如何求满足限制的串的个数?需要用状态DP
令f[i,last]
表示当前i个字符已经固定(不一定满足条件),且
[
i
−
15
,
i
]
[i-15,i]
[i−15,i]的状态是last
的情况下,填充
[
i
+
1
,
n
]
[i+1,n]
[i+1,n]使得满足所有B>=i
的限制的种类数
注意不用管
[
1
,
i
]
[1,i]
[1,i]里是什么字符,也不用计算它的可能种数。
这里限制为15是因为大数据集里B-A<=15
,
[
i
−
15
,
i
]
[i-15,i]
[i−15,i]长度为16,这就足够能判断B=i
的那些限制是不是满足了
- 边界值是
f[n,last]
,对于每个last
,满足B=n
的所有限制的就为1,不然为0 - 状态转移: 当
f[i,last]
已经计算出来之后,枚举[i-16,i-1]
的可能状态last2
,判断last2
是不是满足所以B=i-1
的条件:- 如果是:
f[i-1, last2] = f[i][last2<<1]+f[i][(last<<1)+1]
- 如果不是:
f[i-1,last2] = 0
- 如果是:
当f
生成完毕之后,我们枚举某个前缀,那么我们只要通过查f
就可以知道这个前缀下有多少合法串了。
此外,由于题目给定
p
≤
1
0
18
p\le10^{18}
p≤1018,所以大于
1
0
18
10^{18}
1018的都当作
1
0
18
+
1
10^{18}+1
1018+1即可,为了不超过long long
的限制。
这道题(对我来讲)真的难到生无可恋,主要是如何处理TLE……,参考这篇答案总结了自己可以优化的点:
- 大型数据结构,尤其是
vector
不要当作参数传递,写作全局变量,每轮重新初始化 - 有两个操作是恒定的:将状态左移一位 和 统计每个状态1的个数,它们可以事先做成表
- 流式输出,每决定一个位就输出一个字符,这样前缀可以直接用
last
来表示,不用再将字符串转数字。
#include <stdio.h>
#include <unordered_map>
#include <string>
#include <iostream>
#include <memory.h>
#include <stdlib.h>
#include <set>
#include <cmath>
#include <vector>
#include <algorithm>
#define MASK 0x10000
#define MAXN 100
using namespace std;
typedef long long lld;
typedef vector<pair<int,int>> CONSTYPE;
const int SMASK = (1<<15)-1;
const lld INF = pow(10,18)+1;
int cnt[MASK], toZero[MASK];
CONSTYPE cons[MAXN];
lld f[MAXN][MASK];
bool ableV(int last, int b) {
for(auto &con:cons[b]) {
if(cnt[last&((1<<con.first)-1)]!=con.second) return false;
}
return true;
}
lld add(lld a, lld b) {
if(a==INF || b==INF) return INF;
return min(INF,a+b);
}
void generateF(int n) {
for(int last = 0;last<MASK;last++)
if(ableV(last,n-1))
f[n-1][last] = 1;
for(int i=n-2;i>=0;--i) {
for(int last=0;last<MASK;++last) {
if(ableV(last,i)) {
f[i][last] = add(f[i+1][toZero[last]],f[i+1][toZero[last]^1]);
}
}
}
}
void solve(int n, lld p) {
memset(f,0, sizeof(lld)*MASK*MAXN);
generateF(n);
int last = 0;
for(int i=0;i<n;++i) {
lld base = f[i][toZero[last]];
if(p>base) {
printf("1");
last = toZero[last]^1;
p -= base;
} else {
last = toZero[last];
printf("0");
}
}
}
int main() {
for(int i=0;i<MASK;++i) {
cnt[i] = cnt[i>>1]+(i&1);
toZero[i] = (i&SMASK)<<1;
}
int t;
scanf("%d",&t);
for(auto round=1;round<=t;++round) {
int n,k;
lld p;
scanf("%d%d%lld",&n,&k,&p);
for(int i=0;i<n;++i)
cons[i].clear();
for(int i=0;i<k;++i) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
cons[b-1].push_back(make_pair(b-a+1,c));
}
printf("Case #%d: ",round);
solve(n,p);
printf("\n");
}
}