[NOI2019] 机器人
CXLVIII.[NOI2019] 机器人
首先发现每个点向左向右能到达的位置就类似笛卡尔树上一个点的代表区间,不同的是这里有多个最大值时选取最右的一个。于是我们可以想到一个DP, 表示区间 的最大值恰为 或不大于 ,两种设的方法均可。我们先以第一种方法为例。
我们有转移式 。直接上即可拿到 分,而如果观察到因为单次转移合法的 数量是 的,因而总会被访问到的状态数不会很多(准确地说, 多一点),因此直接记忆化搜索处理即可拿到 分。
满分做法要猜一个结论:假如把所有 加一,得到的就都是左闭右开的区间 ;然后就可以把整个大区间拆分成数量为 级别的小区间 ,使得每个 都可以由几个小区间拼接得来。可以被证明的是,,对于每个 ,在 时,均是一个不超过 次的多项式。
证明也很简单。首先,对于 的情形,这是显然成立的,对于 的情形,所有的 均为 ,是常数;对于 的情形,均为 ,也是常数。然后尝试归纳。假设对于长度 的区间皆成立,则考虑长度为 的区间 ,其函数值是一堆 的函数和,因而只需证明对于上式中所有 得到的式子次数都不大于 即可。因为对于每个 ,后面的一大坨都是前缀和的形式,而众所周知的是,一个 次多项式的前缀和是 次多项式,也因此两个括号里的东西的次数分别为 ,乘起来时加一块就得到了 次多项式。
为了方便转移,我们需要维护前缀和(这里就与一开始设的不大于 的情形殊途同归了)。因为前缀和是 次多项式,所以我们只需要挑出 ,即最多 个点,插个值就能轻松求出前缀和。
我们显然可以取 此 个点。这就意味着,我们只需DP出前 个点,剩下位置的前缀和就能够很快算出了。需要注意的是, 可能大于 ,这时不能插值,但是我们已经知道了每个位置的值,就直接暴力算前缀和即可。并且,因为下标连续,插值可以做到 。
对于段 , 以前的段的值,也要被计入前缀和,但是因为对一切 它都是常数,所以直接算到前缀和多项式的常数项中即可。
我们来计算复杂度。首先,我们要顺序DP每一段 ,一共 段;其次,对于每一段,我们都需要访问所有合法区间各一次,约共 次;再次,对于每个合法区间,我们都需要 地DP出前 个值,再 地插值更新前缀和,加起来仍是 ;三项乘一块,得到总复杂度 。
然后就是卡常了。注意到拉插时,对于同一个 ,不同的位置计算的插值下标都相同,因而得到的拉格朗日基本多项式 也相同,影响的只有系数,因此可以在一起计算,这样使我本地的常数减少了 ,放到网站上就是吸口氧过与不过的区别(反正NOI也吸氧)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int n,a[310],b[310],fac[310],inv[310],tmp[310],lim;
void ADD(int &x,int y){x+=y;if(x>mod)x-=mod;}
int g[2210][310],pre[2210][610],vis[3010],tot,id[310][310];
void Lagrange(int D,int L,int R){
if(L+n>=R){for(int i=1;i<=tot;i++)pre[i][D]=g[i][lim];return;}
tmp[0]=1;for(int i=1;i<=lim;i++)tmp[i]=1ll*tmp[i-1]*(R-(L+i-1))%mod;
for(int j=1;j<=tot;j++)pre[j][D]=0;
for(int i=lim,j=1;i;j=1ll*j*(R-(L+--i))%mod){
int pmt=1ll*tmp[i-1]*j%mod*inv[i-1]%mod*inv[lim-i]%mod;
if((lim-i)&1)for(int j=1;j<=tot;j++)ADD(pre[j][D],mod-1ll*pmt*g[j][i]%mod);
else for(int j=1;j<=tot;j++)ADD(pre[j][D],1ll*pmt*g[j][i]%mod);
}
}
vector<int>v;
void name(int l,int r){if(l>r||id[l][r])return;id[l][r]=++tot;if(l!=r)for(int i=(l+r-1)/2;i<=(l+r)/2+1;i++)name(l,i-1),name(i+1,r);}
int dfs(int l,int r,int D){
if(l>r)return 0;
int ID=id[l][r];
if(vis[ID]==D)return ID;vis[ID]=D;
if(l==r){
if(D<a[l]||D>=b[l]){for(int i=0;i<=lim;i++)g[ID][i]=pre[ID][D-1];pre[ID][D]=pre[ID][D-1];}
else{for(int i=0;i<=lim;i++)g[ID][i]=i+pre[ID][D-1];pre[ID][D]=v[D]-v[a[l]-1];}
return ID;
}
for(int i=1;i<=lim;i++)g[ID][i]=0;g[ID][0]=pre[ID][D-1];
for(int i=(l+r-1)/2;i<=(l+r)/2+1;i++){
if(D<a[i]||D>=b[i])continue;
int A=dfs(l,i-1,D),B=dfs(i+1,r,D);
for(int j=1;j<=lim;j++)ADD(g[ID][j],1ll*g[A][j]*g[B][j-1]%mod);
}
for(int j=1;j<=lim;j++)ADD(g[ID][j],g[ID][j-1]);
return ID;
}
int main(){
// freopen("robot.in","r",stdin);
// freopen("robot.out","w",stdout);
scanf("%d",&n);
fac[0]=1;for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=ksm(fac[n]);for(int i=n-1;i>=0;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),b[i]++,v.push_back(a[i]),v.push_back(b[i]);
sort(v.begin(),v.end()),v.resize(unique(v.begin(),v.end())-v.begin());
for(int i=1;i<=n;i++)a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1,b[i]=lower_bound(v.begin(),v.end(),b[i])-v.begin()+1;
// for(auto i:v)printf("%d ",i);puts("");
// for(int i=1;i<=n;i++)printf("[%d,%d)\n",a[i],b[i]);
name(1,n);
for(int i=0;i<=n+1;i++)g[0][i]=1;
for(int i=1;i<v.size();i++){
// printf("%d\n",i);
lim=min(n+1,v[i]-v[i-1]);
for(int l=1;l<=n;l++)for(int r=l;r<=n;r++)if(id[l][r])dfs(l,r,i);
Lagrange(i,v[i-1],v[i]-1);
// for(int l=1;l<=n;l++)for(int r=l;r<=n;r++)if(id[l][r]){printf("[%d,%d]",l,r);for(int j=0;j<lim;j++)printf("%d ",g[id[l][r]][j]);puts("");}
// puts("");
}
printf("%d\n",pre[id[1][n]][v.size()-1]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?