[NOI2019] 机器人
听说和今年联合省选 \(T2\) 蛮像。
前置题目:
[APIO2016]划艇
先考虑暴力 \(dp\)
设 \(f_{i,j}\) 为前 \(i\) 所学校中,第 \(i\) 所学校参赛,并派出 \(j\) 艘划艇的方案数,这样答案即 \(\sum\sum f_{i,j}\)
转移为:
\(f_{0,1} = 1\)
\(f_{i,j} = \sum_{k = 1}^{j - 1}\sum_{p = 0}^{i - 1}f_{p,k}\)
考虑第二维大小很大,我们对其离散化:
这样变成了 \(O(n)\) 个区间,我们考虑对这些区间操作:
\(f_{i,j}\) 表示最后选取的学校的个数所落在\(j\)区间的个数
那么有:\(f_{i,j} = \sum_{k = 1}^{j - 1}\sum_{p = 0}^{i - 1} \binom{L + m - 1}{m} f_{p,k}\)
最后对后者中的前缀求和即可。
那么我们对应来这个题:
考虑如果我们使用区间\(dp\),每次选取最右边的最大值,则其成功将其划分了子问题,因为左侧无法通过来到右侧,右侧也无法来到左侧。
而且考虑到我们这个最大值,显然其可以做到左右两边,所以其在区间中可以选的位置 \(O(1)\) 个
打个表发现实际上只有 \(2000\) 个左右的区间会被用到。
设 \(f_{[l,r],j}\) 为 \([l,r]\) 上,最大值为 \(j\) 的方案数。
那么我们可以暴力 \(dp\) 一下,大概是 \(O(n^2m)\).
点击查看代码
inline void solve(int l,int r){
if(vis[l][r])return;
int now = ++ cnt;vis[l][r] = now;
if(l > r)dp[now][0] = 1;
else{
for(int i = l;i <= r;++i){
if(abs(i - l - r + i) <= 2){
solve(l,i - 1),solve(i + 1,r);
for(int j = a[i];j <= b[i];++j)
dp[now][j] = (dp[now][j] + dp[vis[l][i - 1]][j] * dp[vis[i + 1][r]][j - 1] % mod) % mod;
}
}
}
for(int i = 1;i <= m;++i)
dp[now][i] = (dp[now][i] + dp[now][i - 1]) % mod;
}
接下来,我们大胆猜想:
在一个固定值域上 \([x,y]\)
\(f_{[l,r],..}\) 是一个关于 \(k\) 的 \(r - l + 1\) 项多项式。
证明过程略去,可以参照其他博客。
那么考虑因为每个点的值域不同,我们采用前置题目的做法:
将其分为 \(O(n)\) 段 \([c_t,c_{t+1})\),对每一段都操作
具体操作是:对每一段都暴力\(dp\)求出前 \(\min(n,len)\) 个点值,然后插值出 \(f_{[l,r],c_{t + 1} - 1}\),作为操作下一段的常数操作,感觉看代码可能更清晰一些。
前置知识:下列代码用到了给定 \(g_{1,...k}\) 点值时的线性插值:
\(g_n = \sum_{i = 1}^k (-1) ^ {k - i} y_i \frac{pre_{i - 1}\ \ \ suf_{i + 1}}{(i-1)!(k - i)!}\),
其中 \(pre_i = \prod_{j = 1}^i (n - j)\),
\(suf = \prod_{j = i}^k(n - j)\)
复杂度大概:\(O(n^2tot)\)
点击查看代码
// #include <bits/stdc++.h>
// using namespace std;
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <ctime>
using namespace std;
typedef long long ll;
const int N = 305, S = 2222, MOD = 1e9 + 7;
int n, a[N], b[N], num[N << 1], fac[N], ifac[N], id[N][N], cnt, len, f[S][N], t, tmp[N];
struct segment { int l, r; } seg[S];
bool vis[S];
int Qpow(int a, int p)
{
int res = 1;
while(p)
{
if(p & 1) res = (ll)res * a % MOD;
a = (ll)a * a % MOD; p >>= 1;
}
return res;
}
void Get(int l, int r)
{
if(l > r || id[l][r]) return;
id[l][r] = ++cnt; seg[cnt] = (segment){l, r};
int mid = l + r >> 1;
for(int i = max(l, mid - 2); i <= min(r, mid + 2); i++) if(abs(i + i - l - r) <= 2)
Get(l, i - 1), Get(i + 1, r);
}
void Solve(int l, int r)
{
if(l > r || vis[id[l][r]]) return;
vis[id[l][r]] = true;
int mid = l + r >> 1;
for(int i = max(l, mid - 2); i <= min(r, mid + 2); i++) if(abs(i + i - l - r) <= 2 && a[i] <= t && t < b[i])
{
Solve(l, i - 1); Solve(i + 1, r);
for(int j = 1; j <= len; j++)
f[id[l][r]][j] = (f[id[l][r]][j] + (ll)f[id[l][i - 1]][j] * f[id[i + 1][r]][j - 1]) % MOD;
}
for(int j = 1; j <= len; j++) if((f[id[l][r]][j] += f[id[l][r]][j - 1]) >= MOD) f[id[l][r]][j] -= MOD;
}
void Lagrange(int l, int r)
{
if(r - l <= n)
{
for(int i = 1; i <= cnt; i++) f[i][0] = f[i][r - l + 1];
return;
}
tmp[n + 1] = 1;
for(int i = n; i; i--) tmp[i] = (ll)tmp[i + 1] * (r - l - i) % MOD;
for(int i = 1; i <= cnt; i++) f[i][0] = 0;
ll prod = 1;
for(int i = l; i <= l + n; i++)
{
ll res = prod * tmp[i - l + 1] % MOD * ifac[i - l] % MOD *
((l - n + i) & 1 ? MOD - ifac[l + n - i] : ifac[l + n - i]) % MOD;
for(int j = 1; j <= cnt; j++) f[j][0] = (f[j][0] + res * f[j][i - l + 1]) % MOD;
prod = prod * (r - i) % MOD;
}
}
int main()
{
// freopen("robot.in", "r", stdin);
// freopen("robot.out", "w", stdout);
scanf("%d", &n); Get(1, n); // fprintf(stderr, "cnt = %d\n", cnt);
for(int i = 1; i <= n; i++)
scanf("%d %d", &a[i], &b[i]), num[++num[0]] = a[i], num[++num[0]] = ++b[i];
sort(num + 1, num + num[0] + 1);
num[0] = unique(num + 1, num + num[0] + 1) - num - 1;
for(int i = 1; i <= n; i++)
a[i] = lower_bound(num + 1, num + num[0] + 1, a[i]) - num,
b[i] = lower_bound(num + 1, num + num[0] + 1, b[i]) - num;
fac[0] = 1;
for(int i = 1; i < N; i++) fac[i] = (ll)fac[i - 1] * i % MOD;
ifac[N - 1] = Qpow(fac[N - 1], MOD - 2);
for(int i = N - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
fill(f[0], f[0] + N, 1);
for(t = 1; t < num[0]; t++)
{
len = min(n + 1, num[t + 1] - num[t]);
memset(vis, false, sizeof(vis));
for(int i = 1; i <= cnt; i++) Solve(seg[i].l, seg[i].r);
Lagrange(num[t], num[t + 1] - 1);
for(int i = 1; i <= cnt; i++) memset(f[i] + 1, 0, len * sizeof(int));
}
printf("%d\n", f[1][0]);
fprintf(stderr, "%.8lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
return 0;
}