Codeforces 924F - Minimal Subset Difference(状态压缩+数位 DP)
好题。目前只有代码,没有口胡(因为感觉题解区里唯一一篇题解对着官方题解翻译的痕迹过于明显了)
首先很明显,此题 \(f(x)\) 只能通过 DP 求得,因此 DP 套 DP 这一步肯定是逃不掉的,因此此题最大的难点肯定在于 DP 套 DP 这一层状态的设计。一个非常 naive 的想法是状压 DP 数组,但是很明显这样状态太多太多了,直接 T 飞。
一个观察是我们只用在乎一个数目前用到的数的可重集,而根据插板法这样的数的集合只有 \(\dbinom{27}{9}\) 个,大约是 \(3\times 10^6\) 级别的,即我们记 \(dp_{len,k,S}\) 表示还有 \(len\) 位要填,有多少种填后 \(len\) 位的方法使得最终的 \(f(x)=k\),转移就枚举下一位即可。而发现每次查询只要调用预处理的结果即可,这样复杂度大约是 \(3\times 10^6·18·10·10+5\times 10^4·10·10·\log(3\times 10^6)\)(其中 \(\log\) 在于 std::map
之类的东西),还是差那么一点点的样子。
接下来一步观察就需要靠打表发现了,我们考虑所有 \(<10^{18}\) 且各位数字单调不降的数,可以发现这里面 \(f(0),f(1)\) 占大多数,具体来说我写了个打表程序,结果如下:
vector<int> vec; int c[11];
int calc(vector<int> v) {
int sum = 0, res = 0; for (int x : v) sum += x;
bitset<83> bs; bs[0] = 1; for (int x : v) bs = bs | bs << x;
for (int i = 0; i * 2 <= sum; i++) if (bs[i]) res = sum - i * 2;
return res;
}
void dfs(int x, int pre) {
if (x == 19) return c[calc(vec)]++, void();
for (int i = pre; i <= 9; i++) vec.pb(i), dfs(x + 1, i), vec.ppb();
}
int main() {
dfs(1, 0); for (int i = 0; i <= 9; i++) printf("%d%c", c[i], " \n"[i == 9]);
return 0;
}
输出
2330403 2339924 12291 2665 877 383 173 74 26 9
可以看到 \(f(k)\ge 2\) 的数只有 \(2\times 10^4\) 个,而进一步爆搜可以发现通过加入某些数可以到达 \(f(k)\ge 2\) 的数只有 \(32175\) 个。这就给我们一个启发:只对 \(k\ge 2\) 的集合进行 DP,这样 DP 有值的个数大约是 \(S\log V\) 的,DP 的复杂度就降到了 \(32175·18·10·10\)。那 \(f(x)=0/1\) 怎么办呢?容斥。注意到还有一个性质:\(f(x)\) 与 \(s(x)\) 的奇偶性相同,其中 \(s(x)\) 为 \(x\) 的各位数字之和。这样一来思路就比较明显了?对于 \(k\ge 1\) 拿总数减掉 \(f(x)>k\) 的贡献,\(k=0\) 就拿总数减掉 \(k\) 是奇数(等价于各位数字和是奇数)和 \(k\ge 2\) 且为偶数的数。这样就不用关心 \(k=0/1\) 的问题了。
据说这样还是过不了。考虑另一个优化,我们发现查询复杂度明显高于预处理,因此考虑在预处理中新添一维 \(lim\) 表示我们强制要求这一位 \(<lim\),这样查询时就少了一个 \(10\),可以通过。
代码先咕着。
upd:有代码了(虽然调得不成样子了)
原来 if (r == 1e18)
当 \(r\) 非常接近 \(10^{18}\) 时会被判成 true
。长见识了。
typedef __int128_t i128;
const int MAXC = 3.3e4;
const int MAXQ = 5e4;
int qu; struct qry {ll l, r; int k;} q[MAXQ + 5];
vector<pair<ll, int> > states;
unordered_map<ll, int> id; int idcnt; ll rid[MAXC + 5];
int trs[MAXC + 5][10];
ll dp[22][MAXC + 2][11], res[MAXQ + 5];
int calc(i128 dpv, int sum) {for (int i = sum / 2; ; i--) if (dpv >> i & 1) return sum - i * 2;}
void dfs(int x, int pre, ll val, i128 dpv, int sum) {
if (x == 19) {
int num = calc(dpv, sum);
if (num >= 2) states.pb(mp(val, num));
return;
}
for (int i = pre; i <= 9; i++) dfs(x + 1, i, val * 10 + i, dpv | (dpv << i), sum + i);
}
void init_states() {
dfs(1, 0, 0, 1, 0); queue<ll> q;
for (auto p : states) id[p.fi] = ++idcnt, rid[idcnt] = p.fi, q.push(p.fi);
while (!q.empty()) {
ll x = q.front(); q.pop(); multiset<int> st;
for (ll tmp = x, d = 0; d < 19; d++, tmp /= 10) st.insert(tmp % 10);
st.insert(0);
for (int d = 0; d < 10; d++) {
if (st.find(d) == st.end()) {trs[id[x]][d] = -1; continue;}
st.erase(st.find(d)); ll nval = 0;
for (int dd : st) nval = nval * 10 + dd;
if (!id[nval]) id[nval] = ++idcnt, rid[idcnt] = nval, q.push(nval);
trs[id[x]][d] = id[nval]; st.insert(d);
}
}
}
vector<tuple<int, int, int, int, int> > qv[22];
int d[MAXQ * 2 + 5][22], len[MAXQ * 2 + 5], id_pre[MAXQ * 2 + 5][22];
void deal_bits(int t, ll x) {
while (x) d[t][len[t]++] = x % 10, x /= 10;
vector<int> vec;
for (int i = len[t]; ~i; i--) {
if (i != len[t]) vec.pb(d[t][i]);
sort(vec.begin(), vec.end()); ll val = 0;
for (int dig : vec) val = val * 10 + dig;
id_pre[t][i] = id[val];
}
}
ll solve_odd(ll id) {
static ll dp0[22][2][2]; memset(dp0, 0, sizeof(dp0));
dp0[len[id]][1][0] = 1;
for (int i = len[id]; i; i--) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) {
int up = (j) ? d[id][i - 1] : 9;
for (int t = 0; t <= up; t++) dp0[i - 1][j && (t == up)][k ^ (t & 1)] += dp0[i][j][k];
}
return dp0[0][0][1] + dp0[0][1][1];
}
ll solve(int id) {
ll ans = 0;
for (int i = len[id] - 1; ~i; i--) ans += dp[i + 1][id_pre[id][i + 1]][d[id][i]];
ans += dp[0][id_pre[id][0]][10];
return ans;
}
void calc_dp(int num) {
memset(dp, 0, sizeof(dp));
int pre = 0, cur = 1;
for (pair<ll, int> p : states) if (p.se == num) dp[0][id[p.fi]][10] = 1;
for (int i = 0; i < 19; i++) {
for (int k = 1; k <= idcnt; k++) {
for (int d = 1; d <= 10; d++) dp[i][k][d] += dp[i][k][d - 1];
if (dp[i][k][10]) {
for (int d = 0; d < 10; d++) {
if (!~trs[k][d]) continue;
dp[i + 1][trs[k][d]][d + 1] += dp[i][k][10];
}
}
}
}
}
int main() {
init_states(); read(qu);
for (int i = 1; i <= qu; i++) {
read(q[i].l); read(q[i].r); read(q[i].k); res[i] = q[i].r - q[i].l + 1;
if (q[i].r == 1000000000000000000ll) res[i] -= (q[i].k == 0), q[i].r--;
deal_bits(i, q[i].r); deal_bits(i + qu, q[i].l - 1);
if (q[i].k == 0) res[i] -= solve_odd(i) - solve_odd(i + qu);
}
for (int k = 2; k <= 9; k++) {
calc_dp(k);
for (int i = 1; i <= qu; i++) {
if (q[i].k == 0 && (k % 2 == 0)) res[i] -= solve(i) - solve(i + qu);
else if (q[i].k && k > q[i].k) res[i] -= solve(i) - solve(i + qu);
}
}
for (int i = 1; i <= qu; i++) print(res[i], '\n');
print_final();
return 0;
}