本周打了腾讯笔试,本来以为笔试的题目应该不难,以为有ACM基础手撕编程题应该很轻松,但是事实证明自己还是太菜了。
显然一拿到手,肯定要暴力打表,找规律。然后一顿啪啪啪,就有下面这段打表代码,想着笔试数据都很水,腾讯提前批笔试的时候,最后一题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;
}
打表原理很简单,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;
}