zoukankan      html  css  js  c++  java
  • 【洛谷2282】[HNOI2003] 历史年份(线段树优化DP)

    点此看题面

    • 给定一个数字串,让你把它划分成若干段,使得每段数字递增(可以有前导(0)),且在最后一个数尽可能小的前提下,字典序最大。
    • 数据组数(le10^3)(|S|le2 imes10^3)

    暴力动态规划

    首先大体思路自然是先从前往后(DP)一遍求出最后一个数最小的取值,再从后往前(DP)一遍求出字典序最大的序列。

    所以先设(f_i)表示以(i)为右端点时最大的满足条件的左端点做第一次(DP)

    考虑暴力的转移,自然是枚举前一个划分点(j),只要满足([f_j,j])对应的数比([j+1,i])对应的数小,就可以给(f_i)一个(j+1)的贡献。

    然后我们发现这个贡献并不取决于(i),只是取决于(j)

    所以我们使用刷表法转移,变成从每一个(j),去找比([f_j,j])大的([j+1,i]),对它们产生一个相同的贡献(j+1)

    二分+哈希比大小

    注意这道题的一大坑点是可能有前导(0)

    所以我们记录(pre_i)(nxt_i)表示每个位置前一个/后一个非零数的位置(包括自身)。

    那么对于一个从(j)的转移,显然如果除去前导之后,([f_j,j])([j+1,i])的位数小,就必然满足转移条件。

    因此只要找到([f_j,j])([j+1,i])位数恰好一样的(i),显然根据我们预处理出的(nxt)数组,两者除去前导零应该分别对应着([nxt_{f_j},j])([nxt_{j+1},i]),因此(j-nxt_{f_j}=i-nxt_{j+1}Leftrightarrow i=nxt_{j+1}+j-nxt_{f_i})

    要比较这两个数的大小,只要二分+哈希即可,而更大的(i)必然全都可以转移。

    第二次(DP)(简略版)

    第二次(DP)与第一次大体相同,从第一次的(f_n)位置开始向前转移保证最后一个数尽可能小,并设新的(f_i)表示以(i)为左端点时最大的满足条件的右端点。

    初始时还要注意对最后一个数的前导零初始化(f_i=n)

    但是,第二次(DP)在转移过程中对于前导零的处理可能和第一次有些差异,具体细节可见代码,这里就不具体展开了。

    线段树优化(DP)

    发现我们相当于每次将一段后缀内的数向一个数取(max),可以无脑上线段树。

    虽然第一次(DP)中将一段后缀内的数打标记是不用撤销的,不用线段树。但由于第二次(DP)的时候是从后往前,后缀的标记就可能消失,反正前后缀需要的操作都是区间取(max)、单点求值,索性写在一起了。

    代码:(O(Tnlogn))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 2000
    using namespace std;
    int n,f[N+5],pre[N+5],nxt[N+5];char s[N+5];
    struct Hash
    {
    	#define ull unsigned long long
    	#define CU Con ull&
    	ull x,y;I Hash() {x=y=0;}I Hash(CU a) {x=y=a;}I Hash(CU a,CU b):x(a),y(b){}
    	I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
    	I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
    	I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);} 
    	I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;} 
    }h[N+5],pw[N+5],seed(302627441,11743207);
    class SegmentTree
    {
    	private:
    		#define PT CI l=1,CI r=n,CI rt=1
    		#define LT l,mid,rt<<1
    		#define RT mid+1,r,rt<<1|1
    		int V[N<<2];
    	public:
    		I void Build(PT) {if(V[rt]=1,l==r) return;RI mid=l+r>>1;Build(LT),Build(RT);}
    		I int Q(CI x,PT) {if(l==r) return V[rt];RI mid=l+r>>1;return max(x<=mid?Q(x,LT):Q(x,RT),V[rt]);}//单点询问
    		I void U(CI L,CI R,CI v,PT)//区间取min,标记永久化
    		{
    			if(v<V[rt]) return;if(L<=l&&r<=R) return (void)(V[rt]=max(V[rt],v));
    			RI mid=l+r>>1;L<=mid&&(U(L,R,v,LT),0),R>mid&&(U(L,R,v,RT),0);
    		}
    }S;
    I bool Chk(CI l1,CI r1,CI l2,CI r2)//二分+哈希比大小
    {
    	#define G(x,y) (h[y]-h[(x)-1]*pw[(y)-(x)+1])
    	if((r1-l1)^(r2-l2)) return r1-l1<r2-l2;if(G(l1,r1)==G(l2,r2)) return 0;RI l=0,r=r1-l1,mid;//判断长度不同和两数相同
    	W(l^r) mid=l+r-1>>1,G(l1,l1+mid)==G(l2,l2+mid)?l=mid+1:r=mid;return s[l1+r]<s[l2+r];//二分最大的相同位,比较后一位
    }
    int main()
    {
    	RI i,t;W(~scanf("%s",s+1))
    	{
    		for(n=strlen(s+1),i=n,t=n+1;i;--i) s[i]^'0'&&(t=i),nxt[i]=t;
    		for(pw[0]=i=1,t=0;i<=n;++i) h[i]=h[i-1]*seed+s[i],pw[i]=pw[i-1]*seed,s[i]^'0'&&(t=i),pre[i]=t;
    		for(S.Build(),i=1;i<=n;++i) f[i]=S.Q(i),//第一遍DP
    			(t=nxt[i+1]+i-nxt[f[i]])<=n&&(!Chk(nxt[f[i]],i,nxt[i+1],t)&&++t,t<=n&&(S.U(t,n,i+1),0));//向后转移,特判相同长度
    		for(S.Build(),S.U(pre[f[n]-1]+1,n,n),i=f[n];i;--i) f[i]=i^f[n]?S.Q(i):n,//第二遍DP
    			t=pre[max(i-(f[i]-nxt[i]+1)-1,0)]+1,!Chk(nxt[t],i-1,nxt[i],f[i])&&(t=nxt[t]+1),t<i&&(S.U(t,i-1,i-1),0);//向前转移,特判相同长度
    		for(i=t=1;i<=n;++i) putchar(s[i]),i==f[t]&&i^n&&(putchar(','),t=i+1);putchar('
    ');//输出答案,每段右端点处输出逗号
    	}return 0;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    10 查看创建表的语句:show create table emp;
    9 常用命令?
    8 如何查看表中的数据?
    7 查看表结构
    构建之法阅读笔记3
    每日汇报
    每周总结
    每日汇报
    每周总结
    每日总结
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu2282.html
Copyright © 2011-2022 走看看