题意
给你两个数l,m,大小为m的数组a,求[0,l]之间满足以下条件的数x的个数:
对于任何i输入[0,m-1],f(x+i)%2=a[i];f(k):代表k在二进制下1的个数
m的范围<=100,l<=1e18,a[i] = 0/1
思路
显然l的范围1e18,大概率就是数位DP了
- 观察到m是<=100的,因此x+m只会改变后7位置,对于前面的位数,则只会进1,使得前面连续的0变成1;
- 那么只要对前半部分进行数位DP,dp[pos][lim][cnt][d]代表位置在pos处,lim代表有无达到上限,cnt为1代表前面有奇数个1为0代表偶数个1,d代表从pos起向前有偶数还是奇数个1;
- 对于第七位以后的部分,直接暴力计算就好了,统计一下是否进位;
代码
#include
using namespace std;
#define int long long
int a[105];
vector<int> b;
int mp[80][2][2][2];
int mx;
int m, l;
int clc(int lim, int cnt, int h) {
int up = (1 << 7) - 1;
if (lim) up = mx;
int all = 0;
int cc = cnt;
for (int i = 0; i <= up; i++) {
bool flag = 1;
for (int j = 0; j < m; j++) {
cnt = cc;
int res = i + j;
int sum = __builtin_popcount(res);
cnt += sum;
cnt &= 1;
if (res > ((1 << 7) - 1))cnt = (cnt + h) & 1;
if (cnt != a[j])flag = 0;
}
// if (flag)cout << i << '\n';
all += flag;
}
return all;
}
long long DFS(int pos, int lim, int cnt, int h) {
if (mp[pos][lim][cnt][h] != -1)return mp[pos][lim][cnt][h];
long long ans = 0;
if (pos <= 6)return clc(lim, cnt, h);
if (lim) {
for (int i = 0; i <= b[pos]; i++) {
bool ok = (i == b[pos]);
int hh = 0;
if (i == 0)hh = 0;
else
hh = (h + 1) & 1;
ans += DFS(pos - 1, ok, (cnt + (i == 1)) & 1, hh);
}
} else {
for (int i = 0; i <= 1; i++) {
// bool ok = (i == b[pos]);
int hh = 0;
if (i == 0)hh = 0;
else
hh = (h + 1) & 1;
ans += DFS(pos - 1, 0, (cnt + (i == 1)) & 1, hh);
}
}
return mp[pos][lim][cnt][h] = ans;
}
void run() {
cin >> m >> l;
for (int i = 0; i < m; i++) {
cin >> a[i];
}
b.clear();
memset(mp, -1, sizeof mp);
for (int i = l; i; i >>= 1) {
b.emplace_back(i & 1);
}
mx = l & ((1 << 7) - 1);
cout << DFS(b.size() - 1, 1, 0, 0) << '\n';
}
signed main() {
int t;
cin >> t;
while (t--)
run();
return 0;
}