2019腾讯春招常规批笔试编程题反思

本周打了腾讯笔试,本来以为笔试的题目应该不难,以为有ACM基础手撕编程题应该很轻松,但是事实证明自己还是太菜了。

题目

暂时还做不了,但是有两张图片
2019腾讯春招常规批笔试编程题反思_第1张图片
2019腾讯春招常规批笔试编程题反思_第2张图片

显然一拿到手,肯定要暴力打表,找规律。然后一顿啪啪啪,就有下面这段打表代码,想着笔试数据都很水,腾讯提前批笔试的时候,最后一题O(n *n!)复杂度都能过,大力出奇迹啊,一交……通过50%。

#include
using namespace std;
typedef long long LL;
int main() {
    LL n, m, k;
    while(cin >> m >> k){
        n = 2 * m;
        LL ans = 0;
        for(LL i = (1 << m) - 1; i < (1 << n);) {
            vector<LL>a,b;
            for(LL j = 0; j < n; j++) {
                if(i & (1 << j)) {
                    a.push_back(j + 1);
                }
                else b.push_back(j + 1);
            }
            LL flag = 1;
            for(LL p = 0; p < m; p++) {
                if(abs(a[p] - b[p]) < k) {flag = 0; break;}
            }
            if(flag) {
                ans++;
            }
            LL x = i & (-i), y = i + x;
            i = (((i & ~y) / x ) >> 1) | y;
        }
        cout << ans << endl;
    }
    return 0;
}

2019腾讯春招常规批笔试编程题反思_第3张图片
打表原理很简单,n = 2 * m, 枚举n个元素,m大小的所有子集,然后暴力判断。

打完表以后想着是不是可以oeis搜一下,然后没有结果,然后又试了一下暴力打表 + BM,均无法通过。然后开始自闭,最后也没有做出来。

笔试后,心里老想着这个题,问了南京大学的Acesrc和北航的Chielo(感谢两个大佬给予的巨大帮助),然后思考了一天终于做出来。虽然交不了,但是和我打表的答案是一致的。

以后要得到经验,这种什么看起来像多项式之和,然后oeis搜不到的必然是什么花里胡哨的dp,所以这个题是一个O((n - k) * m * (1 << k))的状态压缩dp 。

做出这个题有两个地方很关键1:知道这一道状态压缩dp;2:想到怎么表示状态

从头来顺一遍思路,确定dp之后,我们做如下定义,我们定义A组的序列长度为la, B组的序列长度为lb,dp[i][la][s]表示前i个数,A组序列长度为la(因为la确定了lb肯定也确定了),s是一个二进制的数用来表示[i - k + 1, i]区间内的数在A,B组的分配情况。(1表示在A组,0表示在B组)

然后我们思考怎么转移,我们发现dp[i][la][s]在四种情况下有可能对其他状态产生贡献
1、第 i + 1个数放在A组,且当前la >= lb,这种情况显然可以直接放。
所以我们得到第一种转移:dp[i + 1][la + 1][(s & x) << 1 | 1] += dp[i][la][s];

2、第 i + 1个数放在A组,且当前la < lb,这种情况下我们就需要思考一下能不能放了,因为你放下去有可能导致不符合题目中上下差绝对值大等于k的条件,所以这个时候就需要s这个状态起作用了,s表示的是[i - k + 1, i] 这个区间内的数在A,B组中分配情况,我们可以统计其中1和0的数量快速得知这一信息,
设c1为1的数量,c0为0的数量,我们发现如果lb - la - c0 > 0那么这个地方就是可以放下去的,可以画一个图看一下,这么放下去,必然和他所对应的值差值大等于k
所以我们得到第二种转移: if(lb - la - c0 > 0) dp[i + 1][la + 1][(s & x) << 1 | 1] += dp[i][la][s];

3、第i + 1 个数放在B组,且当前lb >= la, 类比情况1,这种情况也是可以直接放的
所以我们得到第三种转移:dp[i + 1][la][(s & x) << 1] += dp[i][la][s];

4、第i + 1个数放在B组,且当前lb < la,这个情况和情况2就是对称,类比一下。
所以我们得到第四种转移:if(la - lb - c1 > 0) dp[i + 1][la][(s & x) << 1] += dp[i][la][s];

边界初始化一下dp[k][k][(1 << k) - 1] = dp[k][0][0] = 1;表示一开始全在A组合全在B组两种情况,

其实还是不行这个题极限数据爆long long,可以尝试int128,或者python搞一下,这不里就不写了

代码

#include
using namespace std;
typedef long long LL;
LL dp[102][51][1 << 11];
int main() {
    LL n, m, k; 
    cin >> m >> k;
    n = 2 * m;
    dp[k][k][(1 << k) - 1] = dp[k][0][0] = 1;
    int x = (1 << (k - 1)) - 1;
    for(LL i = k; i <= n; i++) {
        for(LL la = 0; la <= min(i, m); la++) {
            LL lb = i - la; 
            for(int s = 0; s < (1 << k); s++) {
                 int c1 = __builtin_popcount(s & x), c0 = k - 1 - c1;
                 if(la + 1 <= m) { //放在a
                     if(la >= lb) //a比b长时
                        dp[i + 1][la + 1][(s & x) << 1 | 1] += dp[i][la][s];
                     else {// a比b短时
                         if(lb - la - c0 > 0) {
                             dp[i + 1][la + 1][(s & x) << 1 | 1] += dp[i][la][s];
                         }
                     }
                 }
                 if(lb + 1 <= m) { // 放在b
                     if(lb >= la) { // b比a长时
                         dp[i + 1][la][(s & x) << 1] += dp[i][la][s];
                     }
                     else {// b比a短时
                        if(la - lb - c1 > 0) {
                            dp[i + 1][la][(s & x) << 1]  += dp[i][la][s];
                        }
                     }
                 }
            }
        }
    }
    LL ans = 0;
    for(int s = 0; s < (1 << k); s++) {
        ans += dp[n][m][s];
    }
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(2019腾讯春招常规批笔试编程题反思)