[NOI2019] 机器人
题面
得分情况
100pts: 10
95pts: 一些
75/80/85pts: 几个
60pts: 一些
50pts: 很多
45pts: 少量
35pts: 不少
20pts: 暴多
10pts: 一堆
0pts: 巨多
(上述数据及描述性语言来自NOI讲题人的ppt)
题解
20pts: \(n,B_i\leq 7\)
\(B_i\)和\(n\)都非常小,暴力模拟即可
35pts: \(B_i\leq 100\)
假设一个全局最高点\(p\),钦定\([1,p-1]\)内任意点高度小于等于\(p\)的高度,\([p+1,n]\)内任意点的高度小于\(p\)的高度,显然,\(p\)将\([1,n]\)分割为\([1,p-1]\)和\([p+1,n]\)两个子问题。由此可以区间DP。
设\(dp_{l,r,x}\)表示\([l,r]\)最大值恰好为x时的方案数,暴力枚举断点和最大值转移,容易得到:
利用前缀和优化
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
using namespace std;
const int N=301;
const ll M=1e9+7;
int n,A[N],B[N],dp[N][N][110];
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int MAX=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&A[i],&B[i]),MAX=max(MAX,B[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=MAX;j++){
if(j>=A[i]&&j<=B[i]) dp[i][i][j]++;
dp[i][i][j]=(dp[i][i][j]+dp[i][i][j-1])%M;
}
}
for(int len=2;len<=n;len++)
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<=j;k++)
if(abs((k-i)-(j-k))<=2)
for(int w=A[k];w<=B[k];w++){
if(k==i) dp[i][j][w]=(dp[i][j][w]+dp[k+1][j][w-1])%M;
else if(k==j) dp[i][j][w]=(dp[i][j][w]+dp[i][k-1][w])%M;
else dp[i][j][w]=(dp[i][j][w]+1ll*dp[i][k-1][w]*dp[k+1][j][w-1]%M)%M;
}
for(int k=1;k<=MAX;k++)
dp[i][j][k]=(dp[i][j][k]+dp[i][j][k-1])%M;
}
printf("%d\n",dp[1][n][MAX]);
return 0;
}
50pts: \(B_i\leq 10^4\)
注意到用到的区间数远小于\(n^2\),只对有用的区间进行转移即可。时间与空间耗费有所降低,足以通过\(B_i\leq 10^4\)的测试点。
这里可以采用递归实现。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
#define mul(x,y) (1ll*x*y%M)
#define pls(x,y) ((x+y)%M)
using namespace std;
const int N=305;
const ll M=1e9+7;
int n,idx=0,MAX=0,A[N],B[N],id[N][N],dp[2520][10001];
void solve(int l,int r){
if(id[l][r]) return ;
id[l][r]=++idx;
if(l>r) dp[id[l][r]][0]=1;
else{
for(int k=l;k<=r;k++)
if(abs((k-l)-(r-k))<=2){
solve(l,k-1);solve(k+1,r);
for(int j=A[k];j<=B[k];j++)
dp[id[l][r]][j]=pls(dp[id[l][r]][j],mul(dp[id[l][k-1]][j],dp[id[k+1][r]][j-1]));
}
}
for(int j=1;j<=MAX;j++) dp[id[l][r]][j]=pls(dp[id[l][r]][j],dp[id[l][r]][j-1]);
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&A[i],&B[i]),MAX=max(MAX,B[i]);
solve(1,n);
printf("%d\n",dp[id[1][n]][MAX]);
return 0;
}
60pts:\(A_i=1,B_i=10^9\)
前50pts采用以上所述的区间DP,考虑10pts的特殊点:\(A_i=1,B_i=10^9\)。
因为\(A_i=A_j,B_i=B_j\),任意两点等价,不用考虑值域变化对不同点的不同影响。
当\(l=r\)时,\(dp_{l,r,x}\)的最高项为\(x^0\),其前缀和最高项为\(x^1\).观察dp时对区间合并的过程,显然\(dp_{l,r,x}\)的前缀和是\((r-l+1)\)次多项式。
要求\(dp_{1,n,V}\),递归dp求出前\(n+1\)项,然后拉插即可。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
#define mul(x,y) (1ll*x*y%M)
#define pls(x,y) ((x+y)%M)
using namespace std;
const int N=305;
const ll M=1e9+7;
int n,idx=0,MAX=0,A[N],B[N],id[N][N],dp[2520][10001];ll pw[N],cir[N],f[2520][55];
ll ftp(ll b,ll p){
ll r=1;
while(p){
if(p&1) r=r*b%M;
b=b*b%M;
p>>=1;
}
return r%M;
}
void solve(int l,int r){
if(id[l][r]) return ;
id[l][r]=++idx;
if(l>r) dp[id[l][r]][0]=1;
else{
for(int k=l;k<=r;k++)
if(abs((k-l)-(r-k))<=2){
solve(l,k-1);solve(k+1,r);
for(int j=A[k];j<=B[k];j++)
dp[id[l][r]][j]=pls(dp[id[l][r]][j],mul(dp[id[l][k-1]][j],dp[id[k+1][r]][j-1]));
}
}
for(int j=1;j<=MAX;j++) dp[id[l][r]][j]=pls(dp[id[l][r]][j],dp[id[l][r]][j-1]);
}
void sol1(){
solve(1,n);
printf("%d\n",dp[id[1][n]][MAX]);
}
void DP(int l,int r){
if(id[l][r]) return;
id[l][r]=++idx;
if(l>r) dp[id[l][r]][0]=1;
else{
for(int k=l;k<=r;k++)
if(abs((k-l)-(r-k))<=2){
DP(l,k-1);DP(k+1,r);
for(int j=1;j<=n+1;j++)
dp[id[l][r]][j]=pls(dp[id[l][r]][j],mul(dp[id[l][k-1]][j],dp[id[k+1][r]][j-1]));
}
}
for(int j=1;j<=n+1;j++) dp[id[l][r]][j]=pls(dp[id[l][r]][j],dp[id[l][r]][j-1]);
}
ll lang(){
ll val=1,res=0,val2=0;
for(int i=1;i<=n+1;i++){
val=dp[id[1][n]][i];val2=1;
for(int j=1;j<=n+1;j++)
if(i!=j) val=1ll*val*((MAX-j)%M)%M,val2=1ll*val2*((i-j+M)%M)%M;
val=val*ftp((val2%M+M)%M,M-2)%M;
res=(res+val)%M;
}
return res;
}
void sol2(){
pw[0]=1ll;for(int i=1;i<=n+1;i++) pw[i]=pw[i-1]*(ll)i%M;
cir[n+1]=ftp(pw[n+1],M-2);for(int i=n;i>=0;i--) cir[i]=cir[i+1]*(ll)(i+1)%M;
DP(1,n);
printf("%lld\n",lang());
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
bool onenine=1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&A[i],&B[i]),MAX=max(MAX,B[i]);
if(A[i]!=1||B[i]!=(int)(1e9)) onenine=0;
}
if(n<=300&&MAX<=10000){sol1();return 0;}
if(onenine){sol2();return 0;}
return 0;
}
100pts:
此时不再限制\(A_i=A_j\&\&B_i=B_j\)。
每个\(dp_{l,r,x}\)是在值域上的分段函数,若仍直接对整个值域处理,\(dp_{l,r,x}\)的最高项不再是\(x^0\)。
考虑针对每个\(A_i,B_i\),对值域分段,拆分为若干个左闭右开的区间\([c_t,c_{t+1})\)。区间数量是\(O(n)\)的。
逐段dp,每一段只需dp\([c_t,c_t+n-1]\)得到\(n+1\)项,做前缀和,拉插,再进行下一段即可。
注意实现方式。
时间复杂度: \(O(n^2m)\),\(m\)为有用区间数
参考代码:
#include<bits/stdc++.h>
#define re register
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
typedef unsigned int uit;
const int N=302,M=1e9+7;
template<typename T> void read(T &x){
x=0;bool flag=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') flag=0;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=flag?x:-x;
}
int n,m,idx=0,L,T,A[N],B[N],LS[N<<1],id[N][N],dp[50010][310],pw[N],cir[N],tmp[N];bool vis[N][N];
inline void add(re int &x,re int y){
(x+=y)>=M?x-=M:x;
}
inline int ftp(ll b,ll p){
int r=1;
while(p){
if(p&1) r=1ll*r*b%M;
b=1ll*b*b%M;
p>>=1;
}
return r%M;
}
inline void init(re int l,re int r){
if(l>r||id[l][r]) return;
id[l][r]=++idx;
for(re int i=max(l,mid-1);i<=min(r,mid+1);i++){
if(abs((i-l)-(r-i))<=2){
init(l,i-1);init(i+1,r);
}
}
}
inline void DP(int l,int r){
if(vis[l][r]||l>r) return ;
vis[l][r]=1;
for(re int i=max(l,mid-1);i<=min(r,mid+1);i++){
if(abs((i-l)-(r-i))<=2){
DP(l,i-1);DP(i+1,r);
if(A[i]<=T&&B[i]>T){
for(re int j=1;j<=L;j++)
add(dp[id[l][r]][j],1ll*dp[id[l][i-1]][j]*dp[id[i+1][r]][j-1]%M);
}
}
}
for(re int j=1;j<=L;j++)
add(dp[id[l][r]][j],dp[id[l][r]][j-1]);
}
inline void lang(re int l,re int r){
if(r-l<=n){
for(re int i=1;i<=idx;i++) dp[i][0]=dp[i][r-l+1];
return ;
}
for(re int i=1;i<=idx;i++) dp[i][0]=0;
tmp[n+1]=1;for(int i=n;i>=0;i--) tmp[i]=1ll*tmp[i+1]*(r-(l+i))%M;
re int val=1,res=0;
for(re int i=l;i<=l+n;i++){
res=1ll*val*tmp[i-l+1]%M*cir[i-l]%M*cir[l+n-i]%M;
if((l+n-i)&1) res=M-res;
for(re int j=1;j<=idx;j++) add(dp[j][0],1ll*res*dp[j][i-l+1]%M);
val=1ll*val*(r-i)%M;
}
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n);
for(re int i=1;i<=n;i++){
read(A[i]);read(B[i]);++B[i];
LS[++m]=A[i];LS[++m]=B[i];
}
sort(LS+1,LS+m+1);m=unique(LS+1,LS+m+1)-LS-1;
for(int i=1;i<=n;i++) A[i]=lower_bound(LS+1,LS+m+1,A[i])-LS,B[i]=lower_bound(LS+1,LS+m+1,B[i])-LS;
pw[0]=1ll;for(re int i=1;i<=n+1;i++) pw[i]=1ll*pw[i-1]*i%M;
cir[n+1]=ftp(pw[n+1],M-2);for(re int i=n;i>=0;i--) cir[i]=1ll*cir[i+1]*(i+1ll)%M;
init(1,n);
for(int i=0;i<=n+1;i++) dp[0][i]=1;
for(T=1;T<m;T++){
L=min(LS[T+1]-LS[T],n+1);
memset(vis,0,sizeof vis);
DP(1,n);
lang(LS[T],LS[T+1]-1);
for(re int i=1;i<=idx;i++) for(re int j=1;j<=L;j++) dp[i][j]=0;
}
printf("%d\n",dp[id[1][n]][0]);
return 0;
}

浙公网安备 33010602011771号