T2980 LR棋盘【Dp+空间/时间优化】

Online Judge:未知

Label:Dp+滚动+前缀和优化

题目描述

有一个长度为1*n的棋盘,有一些棋子在上面,标记为L和R。

每次操作可以把标记为L的棋子,向左移动一格,把标记为R的棋子,向右移动一格,前提条件是目标格子为空。结果任意多次操作之后,棋盘会有多少种不同的状态?

输入

第一行一个字符串,包含'L','R','.'。

输出

输出所有不同的棋盘状态,对1e9+7 求余。

样例

Input

R..

R.L

...R..L..L

R..L......LLR.RRL..RR.....RR.L.RR.....L..R....RR.........R..RLL..RL...RLL.LLRR......RLRRR...R......R

Output

3
    
3
    
22

709788833

Hint

样例2的最终状态为 "R.L",".RL","RL."
对于10%的数据,棋子数为1
对于30%的数据,棋子数不超过2,棋盘长度不超过10
对于40%的数据,棋子数不超过5,棋盘长度不超过10
对于60%的数据,棋子数不超过50,棋盘长度不超过500
对于90%的数据,棋子数不超过100,棋盘长度不超过10000
对于100%的数据,棋子数不超过2000,棋盘长度不超过20000。


题解

40pts

观察到一个性质,棋子与棋子之间的相对位置是不会变的。考虑枚举每个棋子的位置,最后检验一遍。运行次数大约为(C(10,5)*5)

大致代码如下

namespace p40{
	int a[12],ans=0;
	inline bool check(){
		for(int i=1;i<=cnt;++i){
			if(s[pos[i]]=='L'&&a[i]>pos[i])return 0;
			if(s[pos[i]]=='R'&&a[i]<pos[i])return 0;
		}
		return 1;
	}
	void dfs(int x,int lst){
		if(x==cnt+1){if(check())ans++;return;}
		for(int i=lst+1;i<=n;++i){a[x]=i;dfs(x+1,i);}
	}
	void solve(){dfs(1,0);printf("%d
",ans);}
}
int main(){
    scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i){
		if(s[i]=='.')continue;
		pos[++cnt]=i;
	}	
}

100pts

L只能往左边走,R只能往右边走。由此我们可以循环一下计算出每个L,R能到达的最左最右位置((li[i],ri[i]) (i∈[1,cnt]))。

代码如下, 应该可以写得更简洁一点。

void pre(){
	int now=n+1,i,j;
	for(int i=cnt;i>=1;i--)if(s[pos[i]]!='.'){
		ri[i]=now-1;
		if(s[pos[i]]=='L')now=pos[i];
	}
	now=0;
	for(int i=1;i<=cnt;++i)if(s[pos[i]]!='.'){
		li[i]=now+1;
		if(s[pos[i]]=='R')now=pos[i];
	}
	for(int i=1;i<=cnt;++i){
		if(s[pos[i]]=='L')ri[i]=pos[i];
		if(s[pos[i]]=='R')li[i]=pos[i];
	}
}

这样问题变成了:

给定cnt个空,第i个空可以填的数字范围为(li[i])~(ri[i]),问有多少种方案,使得序列严格递增。

之前类似的问题有遇到过:T2988 删除数字【状压Dp+前缀和优化】

下面重新分析一下这类问题。

假如题目令填完的序列单调递增(严格),元素个数为(cnt),元素取值范围为(n)

定义状态(dp[i][j])表示,已经填完了前(i)个,且第(i)个空填的数字大小为(j)时,前面的方案数。(定义成后面,逆推也可以)

  • 最朴素的转移就是枚举i,枚举i取的数字,枚举i-1取的数字,时间复杂度为(O(cntcdot n^2)),空间复杂度为(O(cntcdot n))

  • 时间优化:利用前缀和/后缀和优化,免去枚举i-1取的数这一维,时间复杂度为(O(cntcdot n)),空间复杂度仍然为(O(cntcdot n))

  • 空间优化:比如说对于这道题,如果数组开成(dp[cnt][n]),第10个点会MLE。既然只用到前一个位置的状态,容易想到滚动掉第一维变成(dp[2][n])(tip1),也可以改变转移顺序,这样只用到一维(dp[cnt]),空间上大大优化(tip2)。

综上时间复杂度最优为(O(cntcdot n)),dp数组大小只用开到(cnt)

本题代码如下,预处理函数(pre)已经在前面给出:

代码1:

#include<bits/stdc++.h>
#define mod 1000000007
const int N=20005,maxn=2005;
char s[N];
int n,li[maxn],ri[maxn],cnt,pos[maxn],dp[maxn];
void pre(){}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i)if(s[i]!='.')pos[++cnt]=i;
	pre();
	dp[0]=1;
	for(int i=1;i<=n;i++)for(int j=cnt;j>=1;j--){
		if(li[j]<=i&&i<=ri[j])dp[j]=(dp[j]+dp[j-1])%mod;
	}
	printf("%d",dp[cnt]);
}

奉上赛时代码,用了滚动优化空间,而且是逆推,看起来比较乱

代码2

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
typedef long long ll;
bool nc1;
const int maxn=20005;
char s[maxn];
int n,cnt,pos[2010];
ll dp[2][maxn],sum[maxn];
namespace p40{
    int a[12],ans=0;
    inline bool check(){
        for(int i=1;i<=cnt;++i){
            if(s[pos[i]]=='L'&&a[i]>pos[i])return 0;
            if(s[pos[i]]=='R'&&a[i]<pos[i])return 0;
        }
        return 1;
    }
    void dfs(int x,int lst){
        if(x==cnt+1){if(check())ans++;return;}
        for(int i=lst+1;i<=n;++i){a[x]=i;dfs(x+1,i);}
    }
    void solve(){dfs(1,0);printf("%d
",ans);}
}
inline void Do(ll &x,ll y){
    x+=y;
    if(x>=mod)x-=mod;
}
int li[2010],ri[2010];
bool nc2;
int main(){
//  cout<<(&nc2-&nc1)/1024/1024<<endl;
//  freopen("board.in","r",stdin);
//  freopen("board.out","w",stdout);
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;++i){
        if(s[i]=='.')continue;
        pos[++cnt]=i;
    }   
 
    if(n<=10){
        p40::solve();
        return 0;
    }
    register int now=n+1,i,j;
    for(i=cnt;i>=1;i--)if(s[pos[i]]!='.'){
        ri[i]=now-1;
        if(s[pos[i]]=='L')now=pos[i];
    }
    now=0;
    for(i=1;i<=cnt;++i)if(s[pos[i]]!='.'){
        li[i]=now+1;
        if(s[pos[i]]=='R')now=pos[i];
    }
    for(i=1;i<=cnt;++i){
        if(s[pos[i]]=='L')ri[i]=pos[i];
        if(s[pos[i]]=='R')li[i]=pos[i];
    }
     
    memset(dp,0,sizeof(dp));
    int g=0;
    for(i=li[cnt];i<=ri[cnt];++i)dp[g][i]=1;
    for(i=ri[cnt];i>=1;i--)sum[i]=(sum[i+1]+dp[g][i])%mod;
     
    for(i=cnt-1;i>=1;i--){
        g^=1;memset(dp[g],0,sizeof(dp[g])); 
        for(j=li[i];j<=ri[i];++j)Do(dp[g][j],sum[j+1]);
        memset(sum,0,sizeof(sum));
        for(j=ri[i];j>=1;j--){
            sum[j]=sum[j+1];
            Do(sum[j],dp[g][j]);
        }
    }
     
    printf("%lld
",(sum[li[1]]+mod)%mod);
}
原文地址:https://www.cnblogs.com/Tieechal/p/11603597.html