码迷,mamicode.com
首页 > 其他好文 > 详细

题解 nflsoj552 【六校联合训练 省选 #10】崴

时间:2020-03-01 10:44:57      阅读:89      评论:0      收藏:0      [点我收藏+]

标签:长度   sig   ons   space   如何   getc   反转   typedef   its   

题目链接

考虑区间DP。设\(L[i][j]\)表示当前装置里的数字是\(i-1\),已经确定的答案区间为\([i,j]\),还需要的最少操作次数;\(R[i][j]\)表示当前装置里的数字是\(j+1\),已经确定的答案区间为\([i,j]\),还需要的最少操作次数。

发现左端点相同时答案随区间长度的增加而单调不降,且值域很小(实验表明不超过\(45\)),所以考虑将DP的值域和下标反转。

现在重新定义\(L[v][i]=j\)表示用不超过\(v\)的代价,当前装置里的数字为\(i\)时,向右能询问出的最长的区间为\([i+1,j]\)。也就是最大的一个\(j\)使得原DP数组的值\(\leq v\)。类似地,定义\(R[v][i]=j\)表示当前装置里的数字为\(i\)时,向左能询问出的最长的区间为\([j,i-1]\)

考虑转移。我们按\(v\)的大小分层转移。考虑当前在\(v\)这一层,用刷表法去更新\(v+cost\)这些层。

\(L\)的转移为例:
\[ L[v][i]\rightarrow L[v+dis(j,i)+1][j]\quad (i\geq j,R[v][i]\leq j+1) \]
这个式子的意思是,如果满足括号里的条件,就让\(L[v+dis(j,i)+1][j]\)\(L[v][i]\)\(\max\)。其中\(dis(j,i)\)表示把装置里的数字从\(j\)改到\(i\)需要的最少操作次数。之所以要求\(R[v][i]\leq j+1\),是因为假如当前我们在\(i\)这个位置,手里还剩\(v\)次操作时,如果\(R[v][i]>j+1\),即用\(v\)次操作无法问到区间的左端点\(j+1\),那此时小Y决策时就会把数字故意安排在左边(\(<i\))。在这种情况下,鸽子无法通过把数字从\(j\)改到\(i\),以此在\(v+dis(i,j)+1\)次操作内问出\([j+1,L[v][i]]\)这个区间,也就无法进行这次转移。

朴素的转移是\(O(45\cdot n^2)\)的(\(n=99999\)),考虑优化这个复杂度。

我们枚举\(v\),再枚举\(j\),考虑如何维护出能转移到当前\(j\)的所有\(i\)中的最优解。我们维护一个桶,表示装置里的数字从\(j\)变到\(i\),修改了哪些位,其他没有修改的位又分别是多少。由于每个数码有\(0\sim 9\)\(10\)种取值,我们还要加一个符号表示这一位被修改了,所以这个桶的大小是\(11^5\)。考虑从小到大扫描所有的\(j\),每次把\(R[v][i]=j+1\)的所有\(i\)取出。再枚举从\(j\)变到\(i\)时哪些位被修改了(共\(2^5\)种情况),其它没被修改的位保留\(i\)本身的数字,这样就得到了一个值域在\([0,11^5)\)之间的状态。用\(L[v][i]\)的值去更新对应状态的桶。然后对于当前\(j\),桶里已经存下了对于每个被修改的位的集合,从这些\(i\)转移到\(j\)的最优解。我们直接用这个最优解去更新\(L[v+dis(j,i)+1][j]\)即可。

关于这个桶的部分可以结合代码与注释理解。

这样,每一层转移的复杂度是\(O(2^5\cdot n+11^5)\)。其中\(11^5\)是清空上一层的桶所需要的代价。故总时间复杂度为\(O(45\cdot(2^5\cdot n+11^5))\)

参考代码:

//problem:nflsoj552
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
    if(S==T){
        T=(S=buf)+fread(buf,1,MAXN,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
    #define getchar Fread::getchar
#endif
inline int read(){
    int f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline ll readll(){
    ll f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
/*  ------  by:duyi  ------  */ // dysyn1314
inline void cmax(int &x,int y){x=(y>x)?y:x;}
inline void cmin(int &x,int y){x=(y<x)?y:x;}
const int MAXN=99999;
int F[50][MAXN+5],L[50][MAXN+5],R[50][MAXN+5],maskid[MAXN+5][1<<5],lm[161051];
vector<int>buc[MAXN+5];
/*
maskid[i][s]: i这个数,修改了s这些数位后,达到的状态的编号

对两个数(x,y)之间的修改,
我们视作先把x的某些位抹掉(改成一种0~9以外的字符,表示临时状态),
再把这些临时位改成y对应的数位.
当然,虽然把dis(x,y)拆成了两次操作,我们统计答案时还是只计算1次.
这样拆只是为了方便转移,有点meet in middle的感觉.
这样,0~9,加上临时字符,一共11个字符,有11^5种状态,我们用一个桶存起来,
而这个桶的下标就是"状态编号",即maskid
*/

void DP(){
    memset(F,0xbf,sizeof(F));
    memset(L,0xbf,sizeof(L));
    memset(R,0x3f,sizeof(R));
    for(int i=0;i<=MAXN;++i){
        F[0][i]=L[0][i]=min(MAXN,i+1);
        R[0][i]=max(0,i-1);
        static int w[5];
        for(int j=0,t=i;j<5;++j){
            w[j]=t%10,t/=10;
        }
        for(int s=0;s<32;++s){
            int cur=0;
            for(int j=0;j<5;++j){
                int t=w[j];
                if(s&(1<<j))t=10;
                cur=cur*11+t;
            }
            maskid[i][s]=cur;
        }
    }
    for(int v=0;v<45;++v){
        memset(lm,0xbf,sizeof(lm));
        for(int i=0;i<=MAXN;++i)buc[i].clear();
        for(int i=0;i<=MAXN;++i)buc[R[v][i]].pb(i);
        for(int s=0;s<32;++s){
            int nv=v+__builtin_popcount(s)+1;
            if(nv>45)continue;
            for(int i=0;i<(int)buc[0].size();++i){
                cmax(lm[maskid[buc[0][i]][s]],L[v][buc[0][i]]);
            }
            for(int i=0;i<=MAXN;++i){
                if(i!=MAXN)for(int j=0;j<(int)buc[i+1].size();++j){
                    cmax(lm[maskid[buc[i+1][j]][s]],L[v][buc[i+1][j]]);
                }
                cmax(L[nv][i],lm[maskid[i][s]]);
                cmax(F[nv][i],lm[maskid[0][s]]);
            }
        }
        memset(lm,0x3f,sizeof(lm));
        for(int i=0;i<=MAXN;++i)buc[i].clear();
        for(int i=0;i<=MAXN;++i)if(L[v][i]>=0)buc[L[v][i]].pb(i);
        for(int s=0;s<32;++s){
            int nv=v+__builtin_popcount(s)+1;
            if(nv>45)continue;
            for(int i=0;i<(int)buc[MAXN].size();++i){
                cmin(lm[maskid[buc[MAXN][i]][s]],R[v][buc[MAXN][i]]);
            }
            for(int i=MAXN;i>=0;--i){
                if(i!=0)for(int j=0;j<(int)buc[i-1].size();++j){
                    cmin(lm[maskid[buc[i-1][j]][s]],R[v][buc[i-1][j]]);
                }
                cmin(R[nv][i],lm[maskid[i][s]]);
            }
        }
    }
}
int main() {
    DP();
    cerr<<"ok"<<endl;
    int T=read();while(T--){
        int L=read(),R=read(),ans=0;
        --L;
        while(F[ans][L]<R)ans++;
        cout<<ans<<endl;
    }
    return 0;
}

题解 nflsoj552 【六校联合训练 省选 #10】崴

标签:长度   sig   ons   space   如何   getc   反转   typedef   its   

原文地址:https://www.cnblogs.com/dysyn1314/p/12388421.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!