• 【题解】「NOI2019」机器人 [*hard]


    2019 年的尾巴在 2021 年的钟声即将敲响之际被解决了!

    我的肯定不是最快的,但一定是很短的。

    没看懂其他人在干什么干脆自己瞎推了。

    考虑令 (pos) 表示全局最高点(如果有多个,就选最右边的那个),容易发现 ([1,pos-1]) 的点都无法移动到 (pos) 后面,([pos+1,n]) 的点都无法移动到 (pos) 前面,这下两边就变成了互不影响的区间。可以想到以此来区间 DP 。

    (dp_{l,r}(x)) 表示区间 ([l,r]) 最大值为 (x) 时的方案数,转移的时候枚举一下最大值可能的位置转移即可,注意用前缀和优化。

    这下子的话复杂度是 (O(n^2m)) 的,其中 (m) 为权值最大值,不可接受。考虑到一个区间内可能转移的最大值只有 (O(1)) 个,可能的区间数或许远没有 (n^2) 。打表可以发现 (nleq 300) 时区间数是 (2518) ,这样就可以通过 (50) 分了。

    void solve(int l,int r) {
        if(vis[l][r]) return ;
        int now=++tot; vis[l][r]=now;
    
        if(l>r) dp[now][0]=1;
        else {
            lep(i,l,r) if(abs(i-l-r+i)<=2) {
                solve(l,i-1),solve(i+1,r);
                lep(j,a[i],b[i]) pls(dp[now][j],mul(dp[vis[l][i-1]][j],dp[vis[i+1][r]][j-1]));
            }
        }
        lep(j,1,m) pls(dp[now][j],dp[now][j-1]);
    }
    

    接下来一档分是 (a=1,b=10^9) 的。不妨思考一下,这档 (a,b) 均一样的分意义何在?

    首先将 (a,b) 集体减一。可以发现当 (l=r) 的时候,(dp_{l,r}(x)) 多项式的最高项是 (x^{0}) ,其前缀和的多项式最高项是 (x^{1}) 。当两边合并的时候,继续观察,不难发现 (dp_{l,r}(x)) 的前缀和是 (r-l+1) 次多项式。

    我们的目标是 (dp_{1,n}(m)) ,直接拉插即可。

    满分做法?容易发现比如说 (a_i=3,b_i=5) 时,(dp_{i,i}(x)) 的最高项并不是 (x^{0}) ,称这种情况为"不满足条件"。但是如果将值域段定为 ([3,5]) ,就没问题了。而且可以发现,如果定义一段值域 ([3,4]) 以及一段值域 ([5,5]) ,那么 (dp_{i,i}(x)) ,对于第一段值域满足条件,对于第二段值域仍然满足条件(显然前面值域段做前缀和后对于当且值域段都是常数项)。

    回忆 APIO2016 划艇,对于每个 ([a_i,b_i]) 将其拆分为若干个左闭右开的区间 ([c_t,c_{t+1})) ,容易发现处理后只有 (O(n)) 个左闭右开的区间。

    这样逐段 DP 即可。每一段 DP 只需要 DP ([c_t,c_{t}+n-1]) 这段区间,然后就可以拉插了。具体地,对每个 DP 状态的前缀和都拉插求出其在 (c_{t+1}-1) 时的取值后继续做下一段即可。

    因为拉插时下标连续,所以可以线性拉插,因此拉插一次时间复杂度 (O(n)) 。每一轮一共有 (mleq2518) 个状态需要拉插,一共有 (O(n)) 轮((O(n)) 段左闭右开的区间),因此时间复杂度是 (O(n^2m)) 的,实现精细可以通过。

    不知道为什么 UOJ 上 T 成 95 了。

    // {{{ solve
    
    bool vis[N][N];
    int L,T,tot,id[N][N],dp[S][N];
    
    void init(int l,int r) {
        if(id[l][r]) return ; id[l][r]=++tot;
        if(l<=r) lep(i,l,r) if(abs(i-l-r+i)<=2) init(l,i-1),init(i+1,r);
    }
    void solve(int l,int r) {
        if(vis[l][r]) return ; vis[l][r]=true;
        if(l>r) dp[id[l][r]][0]=1;
        else {lep(i,l,r) if(abs(i-l-r+i)<=2&&(a[i]<=T&&T<b[i])) {
            solve(l,i-1),solve(i+1,r);
            lep(j,1,L) pls(dp[id[l][r]][j],mul(dp[id[l][i-1]][j],dp[id[i+1][r]][j-1]));
        }}
        lep(j,1,L) pls(dp[id[l][r]][j],dp[id[l][r]][j-1]);
    }
    
    // }}}
    
    int tmp[N],invfac[N]; inline void lagrange(const int l,const int r) {
        if(n+l>=r) {lep(t,1,tot) dp[t][0]=dp[t][r-l+1]; return ;}
        tmp[n+1]=1; rep(i,n,1) tmp[i]=mul(tmp[i+1],r-i-l);
        lep(t,1,tot) dp[t][0]=0;
    
        int qwq=1; lep(i,l,l+n) {
            int res=mul(mul(tmp[i-l+1],qwq),mul(
                ((l-n+i)&1)?mod-invfac[l+n-i]:invfac[l+n-i],invfac[i-l]));
            lep(t,1,tot) pls(dp[t][0],mul(res,dp[t][i-l+1]));
            qwq=mul(qwq,r-i);
        }
    }
    
    int c[N<<1]; int main() {
        scanf("%d",&n);
        lep(i,1,n) scanf("%d%d",&a[i],&b[i]),c[++m]=a[i],c[++m]=++b[i];
        std::sort(c+1,c+1+m),m=std::unique(c+1,c+1+m)-c-1;
        lep(i,1,n)
            a[i]=std::lower_bound(c+1,c+1+m,a[i])-c,
            b[i]=std::lower_bound(c+1,c+1+m,b[i])-c;
    
        invfac[0]=1;
        lep(i,1,n+3) invfac[i]=mul(invfac[i-1],i);
        invfac[n+3]=modpow(invfac[n+3],mod-2);
        rep(i,n+3,1) invfac[i-1]=mul(invfac[i],i);
    
        init(1,n); for(T=1;T<m;++T) {
            L=min(c[T+1]-c[T],n+1),memset(vis,0,sizeof(vis));
            lep(l,1,n) lep(r,1,n) if(id[l][r]) solve(l,r);
            lagrange(c[T],c[T+1]-1);
            lep(t,1,tot) lep(i,1,L) dp[t][i]=0;
        }
        printf("%d
    ",dp[vis[1][n]][0]);
        return 0;
    }
    
  • 相关阅读:
    [转]lftp的致命错误:证书验证:不信任
    github每次push都需要密码以及用户名的解决办法
    Fedora最小化安装后没有ifconfig命令
    [转载]MySql常用命令总结
    chrome浏览器强制采用https加密链接
    红帽系列linux自行配置本地yum源
    linux 下dd命令直接清除分区表(不用再fdisk一个一个的删除啦)
    linux分区工具fdisk的使用
    Java多线程实现......(1,继承Thread类)
    第一篇文章--我为什么要写博客?
  • 原文地址:https://www.cnblogs.com/losermoonlights/p/14398240.html
Copyright © 2020-2023  润新知