zoukankan      html  css  js  c++  java
  • CodeForces 1129C

    洛谷题目页面传送门 & CodeForces题目页面传送门

    题意见洛谷里的翻译。

    首先我们可以用区间DP算出对于每个子01串,能表示的字母串的个数。

    (dp_{i,j})表示长度为(i),起点为(j)(下标从(1)开始)的子01串能表示的字母串的个数(也许我设的DP状态有点奇怪)。显然,(dp_{0,i}=1),因为空子01串能且仅能表示空字母串。

    那么转移怎么转移呢?可以从串的首部转移,从首部挖出(1sim4)个字符组成字母,累加上剩下的串的DP值。那么状态转移方程为(dp_{i,j}=sumlimits_{k=1}^{min(4,i)}dp_{i-k,j+k}),其中(k=4)时还要考虑首部(4)个字符是否为不能表示字母的那可怜的(4)个01串,如果是的话,就不能累加上(dp_{i-4,j+4})。同样的道理,从尾部转移也是可以的。转移的总时间复杂度是(mathrm O!left(n^2 ight)),因为一共有(mathrm O!left(n^2 ight))个子01串,而每次转移是(mathrm{O}(1))的。

    接下来要算答案了。设到当前第(i)次的添加字符后,答案为(ans),那么容易想到,下一次(第(i+1)次)添加字符后,增加了([1,i+1],[2,i+1],cdots,[i+1,i+1])(i+1)个子01串,答案变成了(ans+sumlimits_{j=1}^{i+1}dp_{i+1-j+1,j}),我们把(sumlimits_{j=1}^{i+1}dp_{i+1-j+1,j})累加到(ans)里。但是,在累加的过程中,可能对于有的(j)([j,i+1])这段子01串和之前累加的有重复,也就是说这段子01串所能表示的所有字母串都被算过了,就不能再把(dp_{i+1-j+1,j})累加上了。所以我们需要判重。

    我们可以预处理出前缀哈希数组(Hsh),这样可以在(mathrm{O}(1))时间内计算出任意一个子串的哈希值。然后怎么判重呢?用一个(vis)数组肯定不行,因为哈希值在(left[0,2^{64} ight))内,会MLE。用裸的set,要判(mathrm O!left(n^2 ight))次,每次(mathrm O!left(log n^2 ight)=mathrm{O}(log n)),总共(mathrm O!left(n^2log n ight));将set按子01串长度分成(n)组,也还是(mathrm O!left(n^2log n ight))。这复杂度配上STL的常数,会TLE。

    那该怎么办呢?事实上,set的组数其实应该尽可能多,那样每个set的元素个数就尽可能少,复杂度就尽可能小。不妨按哈希值模(10^7)的值分为(10^7)组。这样分,平均每组只有一两个元素,用set还亏了,直接用链式前向星装即可。

    以下是AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define N 3000
    #define hshmod 10000000//将哈希分为的组数
    #define mod 1000000007
    #define ull unsigned long long
    bitset<N+1> a;//01串
    int dp[N+1][N+2];//dp[i][j]表示长度为i,起点为j的子01串能表示的字母串数
    inline bool ok(bool a,bool b,bool c,bool d){//是否能表示字母
    	return !(!a&&!b&&c&&d||!a&&b&&!c&&d||a&&b&&c&&!d||a&&b&&c&&d);//不是那可怜的4个串就OK
    }
    ull Hsh[N+1]/*前缀哈希值*/,power[N+1]/*power[i]为哈希base(131)的i次方*/;
    ull hsh(int l,int r){//[l,r]这段子01串的哈希值
    	return Hsh[r]-Hsh[l-1]*power[r-l+1];
    }
    int head[hshmod],nxt[N*N],tot;ull data[N*N];//链式前向星
    void add(int x,ull y){//往第x组里添加哈希值y
    	data[++tot]=y;nxt[tot]=head[x];head[x]=tot;
    }
    int main(){
    	int n/*添加字符的次数,即最终01串的长度*/,ans=0/*目前的答案*/,i,j;scanf("%d",&n);
    	for(i=1;i<=n;i++){int x;scanf("%d",&x);a[i]=x;}
    	power[0]=1;//131^0=1
    	for(i=1;i<=n;i++)Hsh[i]=Hsh[i-1]*131ull+a[i]+1,power[i]=power[i-1]*131ull;//预处理
    	fill(dp[0]+1,dp[0]+n+2,1);//DP边界:dp[i][0]=1
    	for(i=1;i<=n;i++)for(j=1;j+i-1<=n;j++)//状态转移
    		dp[i][j]=dp[i-1][j+1],
    		dp[i][j]+=i>=2?dp[i-2][j+2]:0,dp[i][j]%=mod,
    		dp[i][j]+=i>=3?dp[i-3][j+3]:0,dp[i][j]%=mod,
    		dp[i][j]+=i>=4&&ok(a[j],a[j+1],a[j+2],a[j+3])?dp[i-4][j+4]:0,dp[i][j]%=mod;
    //	for(i=1;i<=n;i++)for(j=1;j+i-1<=n;j++)
    //		printf("dp[%d][%d]=%d
    ",i,j,dp[i][j]);
    	for(i=1;i<=n;i++){//第i次添加字符
    		for(j=1;j<=i;j++){//枚举新增子01串的左端点,右端点是i
    			ull h=hsh(j,i);//此新子01串的哈希值
    			int hmod=h%hshmod;//它的类别
    //			cout<<h<<" "<<hmod<<" "<<dp[i-j+1][j]<<"
    ";
    			bool mar=false;//是否和之前的某一个串重复
    			for(int k=head[hmod];k;k=nxt[k])//枚举此组里的元素
    				if(data[k]==h)//有和它的哈希值相同的?
    					{mar=true;/*重复*/break;/*不必再找,退出*/}
    			if(!mar)//如果无重复,照样累加
    				ans+=dp[i-j+1][j],ans%=mod,add(hmod,h)/*扔进链表*/;
    		}
    		printf("%d
    ",ans);//输出目前的答案
    //		puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    第一节课课堂总结--付胤
    自我介绍--付胤
    JavaScript面向对象的理解
    与redmine对接
    CCS3属性之text-overflow:ellipsis;的用法和注意之处
    自定义TextView带有各类.ttf字体的TextView
    百度地图sdk的使用
    DrawRightEditText自定义EditText实现有内容时右侧图标按钮显示无内容时右侧图标按钮隐藏加上为空时晃动动画(二)
    DrawRightEditText自定义EditText实现有内容时右侧图标按钮显示无内容时右侧图标按钮隐藏加上为空时晃动动画
    首页底部菜单FragmentTabHost的使用
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-1129C.html
Copyright © 2011-2022 走看看