zoukankan      html  css  js  c++  java
  • [NOI2019] 机器人

    CXLVIII.[NOI2019] 机器人

    首先发现每个点向左向右能到达的位置就类似笛卡尔树上一个点的代表区间,不同的是这里有多个最大值时选取最右的一个。于是我们可以想到一个DP,\(f_{[i,j],k}\) 表示区间 \([i,j]\) 的最大值恰为 \(k\) 或不大于 \(k\),两种设的方法均可。我们先以第一种方法为例。

    我们有转移式 \(f_{[l,r],k}=\sum\limits_{i\text{ which satisfy the constraint}}\Big[k\in[a_i,b_i]\Big]\Big(\sum\limits_{k'\leq k}f_{[l,i-1],k'}\Big)\Big(\sum\limits_{k'<k}f_{[i+1,r],k'}\Big)\)。直接上即可拿到 \(35\) 分,而如果观察到因为单次转移合法的 \(i\) 数量是 \(O(1)\) 的,因而总会被访问到的状态数不会很多(准确地说, \(2100\) 多一点),因此直接记忆化搜索处理即可拿到 \(50\) 分。

    满分做法要猜一个结论:假如把所有 \(b_i\) 加一,得到的就都是左闭右开的区间 \([a_i,b_i)\);然后就可以把整个大区间拆分成数量为 \(O(n)\) 级别的小区间 \([c_1,c_2),[c_2,c_3),\dots,[c_{m-1},c_m)\),使得每个 \([a_i,b_i)\) 都可以由几个小区间拼接得来。可以被证明的是,\(f_{[l,r]}\),对于每个 \(i\),在 \(k\in[c_i,c_{i+1})\) 时,均是一个不超过 \(r-l\) 次的多项式。

    证明也很简单。首先,对于 \(l=r\) 的情形,这是显然成立的,对于 \([c_i,c_{i+1})\subseteq[a,b)\) 的情形,所有的 \(f_{[l,r],k}\) 均为 \(1\),是常数;对于 \([c_i,c_{i+1})\nsubseteq[a,b)\) 的情形,均为 \(0\),也是常数。然后尝试归纳。假设对于长度 \(\leq p\) 的区间皆成立,则考虑长度为 \(p+1\) 的区间 \([l,r]\),其函数值是一堆 \(i\) 的函数和,因而只需证明对于上式中所有 \(i\) 得到的式子次数都不大于 \(r-l\) 即可。因为对于每个 \(i\),后面的一大坨都是前缀和的形式,而众所周知的是,一个 \(n\) 次多项式的前缀和是 \(n+1\) 次多项式,也因此两个括号里的东西的次数分别为 \(i-l,r-i\),乘起来时加一块就得到了 \(r-l\) 次多项式。

    为了方便转移,我们需要维护前缀和(这里就与一开始设的不大于 \(k\) 的情形殊途同归了)。因为前缀和是 \(r-l+1\) 次多项式,所以我们只需要挑出 \(r-l+2\),即最多 \(n+1\) 个点,插个值就能轻松求出前缀和。

    我们显然可以取 \(c_i,c_i+1,c_i+2,\dots,c_i+n\)\(n+1\) 个点。这就意味着,我们只需DP出前 \(n+1\) 个点,剩下位置的前缀和就能够很快算出了。需要注意的是,\(c_i+n\) 可能大于 \(c_{i+1}\),这时不能插值,但是我们已经知道了每个位置的值,就直接暴力算前缀和即可。并且,因为下标连续,插值可以做到 \(O(n)\)

    对于段 \([c_i,c_{i+1})\)\(i\) 以前的段的值,也要被计入前缀和,但是因为对一切 \(k\in[c_i,c_{i+1})\) 它都是常数,所以直接算到前缀和多项式的常数项中即可。

    我们来计算复杂度。首先,我们要顺序DP每一段 \([c_i,c_{i+1})\),一共 \(O(n)\) 段;其次,对于每一段,我们都需要访问所有合法区间各一次,约共 \(2100\) 次;再次,对于每个合法区间,我们都需要 \(O(n)\) 地DP出前 \(n+1\) 个值,再 \(O(n)\) 地插值更新前缀和,加起来仍是 \(O(n)\);三项乘一块,得到总复杂度 \(O(2100n^2)\)

    然后就是卡常了。注意到拉插时,对于同一个 \([c_i,c_{i+1})\),不同的位置计算的插值下标都相同,因而得到的拉格朗日基本多项式 \(l_i\) 也相同,影响的只有系数,因此可以在一起计算,这样使我本地的常数减少了 \(1s\),放到网站上就是吸口氧过与不过的区别(反正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;
    } 
    

  • 相关阅读:
    c#.net分类上升达人~~~呵呵。。。
    js5:框架的使用,使框架之间无痕连接
    搭建 redis 集群 (redis-cluster)
    写好用户故事的10个提示
    什么是用户故事及用户故事在敏捷开发中如何编写
    java开发手册
    性能优化指南:性能优化的一般性原则与方法
    大型网站技术架构:摘要与读书笔记
    用MyEclipse JPA创建项目
    深入了解 JPA
  • 原文地址:https://www.cnblogs.com/Troverld/p/14601716.html
Copyright © 2011-2022 走看看