AtCoder WTF 2019 B Multiple of Nine/南外集训 2024.2.23 T1

给定 q 个区间 {[li,ri]},计算满足条件的长度为 n 的十进制数码串 S 的个数 mod109+7

  • i[1,q],num(S[li,ri])0(mod9)

其中 num(T) 表示数码串 T 代表的整数,T[a,b] 表示子串 TaTa+1Tb。在这道题中,我们允许任意形式的前导零。

1q15,1n109

所有涉及到的前缀和位置和 0 位置是关键位置,我们关心它们的前缀和 mod9 的结果。假设我们已经知道了这些结果,且满足题中条件,考虑有多少种合法序列符合它。此时可以计算两个相邻的关键位置之间的位置的填法数:假设这两个位置间隔 d,则它们填出来的数有 10d19 种可能性 mod9=x[1,8],而有 10d19+1 种可能性 mod9=0。所以我们只关心这两个关键位置的前缀和 mod9 是否相等。我们先用并查集合并题中给出的区间对应的关键位置,这样只剩下 q+1 个块,接下来再去处理这些块的可能的相等关系。

先计算出所有相邻关键位置前缀和都不同时的答案,然后计算出如果存在某个极大等价类,那么它会让答案乘以多少,这可以用 Θ(q2q) 得到。接下来使用相等关系容斥(实际上就是集合幂级数取 ln),得到钦定一个等价类存在的权值,这一部分可以用某些集合幂级数技术做到 Θ(q22q),但是直接 Θ(3n) 也行。最后进行集合划分 DP,复杂度依然是 Θ(q22q)Θ(3q)。注意到我们似乎把 0 位置搞进来了,所以最后需要除以 9,表示 0 位置的这个连通块前缀和必须被钦定成 0。还要算上最后一段没被覆盖到的部分,这些位置显然可以任意填。

// Author: kyEEcccccc
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
#define F(i, l, r) for (int i = (l); i <= (r); ++i)
#define FF(i, r, l) for (int i = (r); i >= (l); --i)
#define MAX(a, b) ((a) = max(a, b))
#define MIN(a, b) ((a) = min(a, b))
#define SZ(a) ((int)((a).size()) - 1)
constexpr int N = 16, MOD = 1000000007;
LL kpow(LL x, ULL k = MOD-2)
{
x = x % MOD;
LL r = 1;
while (k)
{
if (k & 1) r = r * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return r;
}
int len, n;
int a[N], b[N];
vector<int> pos;
int ff[N*2], co[N*2], tot_co;
LL tar[1 << N], coef[1 << N], f[1 << N];
int get_anc(int x)
{
if (x == ff[x]) return x;
return ff[x] = get_anc(ff[x]);
}
signed main(void)
{
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(nullptr);
cin >> len >> n;
pos.assign(1, 0);
F(i, 0, n-1) cin >> a[i] >> b[i], --a[i], pos.push_back(a[i]), pos.push_back(b[i]);
sort(pos.begin(), pos.end());
pos.erase(unique(pos.begin(), pos.end()), pos.end());
F(i, 0, n-1)
{
a[i] = lower_bound(pos.begin(), pos.end(), a[i]) - pos.begin();
b[i] = lower_bound(pos.begin(), pos.end(), b[i]) - pos.begin();
}
F(i, 0, SZ(pos)) ff[i] = i;
F(i, 0, n-1) ff[get_anc(b[i])] = get_anc(a[i]);
F(i, 0, SZ(pos)) if (ff[i] == i) co[i] = tot_co++;
F(i, 0, SZ(pos)) co[i] = co[get_anc(i)];
assert(tot_co <= N);
LL xx = 1;
F(i, 1, SZ(pos))
{
int d = pos[i] - pos[i-1];
LL x = (kpow(10, d) - 1) * kpow(9) % MOD;
(xx *= x) %= MOD;
}
F(w, 1, (1<<tot_co)-1)
{
tar[w] = 1;
F(i, 1, SZ(pos))
{
if (!((w >> co[i] & 1) && (w >> co[i-1] & 1))) continue;
int d = pos[i] - pos[i-1];
LL x = (kpow(10, d) - 1) * kpow(9) % MOD;
(tar[w] *= kpow(x) * (x+1) % MOD) %= MOD;
}
// cerr << w << ' ' << tar[w] << '\n';
coef[w] = tar[w];
int u = __lg(w&-w);
for (int ww = w^1<<u; ww; ww = (ww - 1) & (w^1<<u))
{
(coef[w] -= tar[ww] * coef[w^ww]) %= MOD;
(f[w] += f[ww] * coef[w^ww] % MOD * 9) %= MOD;
}
(f[w] += coef[w] * 9) %= MOD;
}
LL ans = f[(1<<tot_co)-1] * xx % MOD * kpow(10, len - pos.back()) % MOD * kpow(9);
cout << (ans % MOD + MOD) % MOD << '\n';
return 0;
}
posted @   kyEEcccccc  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示