【Kickstart】2018 Round B - Sherlock and the Bit Strings

解法

要求第P个,比较好的方法是求在固定前缀下有多个种可能性,然后递归地增加前缀,最后到结尾。
先不考虑固定前缀的问题,如何求满足限制的串的个数?需要用状态DP
f[i,last]表示当前i个字符已经固定(不一定满足条件),且 [ i − 15 , i ] [i-15,i] [i15,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] [i15,i]长度为16,这就足够能判断B=i的那些限制是不是满足了

  1. 边界值是f[n,last],对于每个last,满足B=n的所有限制的就为1,不然为0
  2. 状态转移: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} p1018,所以大于 1 0 18 10^{18} 1018的都当作 1 0 18 + 1 10^{18}+1 1018+1即可,为了不超过long long的限制。

这道题(对我来讲)真的难到生无可恋,主要是如何处理TLE……,参考这篇答案总结了自己可以优化的点:

  1. 大型数据结构,尤其是vector不要当作参数传递,写作全局变量,每轮重新初始化
  2. 有两个操作是恒定的:将状态左移一位统计每个状态1的个数,它们可以事先做成表
  3. 流式输出,每决定一个位就输出一个字符,这样前缀可以直接用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");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值