CCPC2019 Harbin Site B.Binary Numbers
传送门
题面挺不好懂,简单来说就是有\(0 \sim 2^m-1\)个数被划分成了\(n\)段,从每一段中选出一个数\(a_i\),要满足题中的限制。而一种\(\{a_i\}\)选择方案的价值是这\(n\)个数的乘积,问所有合法的方案的价值之和对\(100000007\)取模的结果。
首先关于题目中的函数\(F_m(a,b)\),其实就是\(a,b\)二进制表示下的最长公共前缀(把最高位看成第一位)。而这个函数实际上是一个单峰函数,即对于\(F_m(a,b)\)来说,当\(b < a\)时,\(b\)越小,\(LCP(a,b)\)越短;当\(b > a\)时,\(b\)越大,\(LCP(a,b)\)越小。
那么如果要满足题中的限制,对于每个区间需要满足:
- \(LCP(a_i,l_i)\geqslant LCP(a_{i-1},l_i)\),
- \(LCP(a_i,r_i)\geqslant LCP(a_{i+1},r_i)\).
可以采用动态规划,状态还是比较巧妙的:令\(dp[i][j][k]\)表示到第\(i\)个区间,\(LCP(a_i,l_{i+1})=j,LCP(a_i,r_i)=k\)时的贡献和。枚举\(a_i\),考虑如何从\(dp[i-1]\)转移到\(dp[i]\):记上一层的状态为\(dp[i-1][j_{lst}][k_{lst}]\),这一维的状态为\(dp[i][j_{now}][k_{now}]\),那么\(j_{lst} \in [0,LCP(a_i,l_i)],k_{lst} \in [LCP(a_i,r_{i-1}),m]\),这个区间中的\(j_{lst}\)和\(k_{lst}\)都可以转移到\(j_{now}\)和\(k_{now}\),乘上位权\(a_i\)即可.
这样时间复杂度是\(O(2^m m^2)\).
#include<bits/stdc++.h>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const ll mod = 100000007;
const int maxn = (1 << 17) + 5;
const int maxm = 18;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
In ll ADD(ll a, ll b) {return a + b < mod ? a + b : a + b - mod;}
int n, m, l[maxn], r[maxn];
In int lcp(int a, int b)
{
for(int i = m - 1; i >= 0; --i)
if(((a >> i) & 1) ^ ((b >> i) & 1)) return m - i;
return m;
}
ll dp[maxn][maxm][maxm];
In ll solve()
{
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= m; ++j) fill(dp[i][j], dp[i][j] + m + 1, 0);
dp[0][0][m] = 1;
for(int i = 1; i <= n; ++i)
{
for(int x = l[i]; x <= r[i]; ++x)
{
int lstl = lcp(x, l[i]), lstr = lcp(x, r[i - 1]);
int nowl = i == n ? 0 : lcp(x, l[i + 1]);
int nowr = lcp(x, r[i]);
for(int j = 0; j <= lstl; ++j)
for(int k = lstr; k <= m; ++k)
dp[i][nowl][nowr] = ADD(dp[i][nowl][nowr], dp[i - 1][j][k] * x % mod);
}
}
ll ans = 0;
for(int j = 0; j <= m; ++j)
for(int k = 0; k <= m; ++k) ans = ADD(ans, dp[n][j][k]);
return ans;
}
int main()
{
int T = read();
while(T--)
{
m = read(), n = read();
for(int i = 1; i <= n; ++i) l[i] = read(), r[i] = read();
write(solve()), enter;
}
return 0;
}