您的位置:首页 > 其它

AtCoder Regular Contest 065 Shuffling 动态规划

2018-02-27 09:39 399 查看

题意

给出一个长度为n的01序列,有m次操作,第i次可以把区间[li,ri]中的元素任意排列。问该序列可以通过操作得到多少种不同的序列。

n,m<=3000,保证li单调不降。

分析

先把操作区间变成左端点和右端点均单调递增,然后设f[i,j]表示前i个位置的元素已经确定,且前i个位置中恰好有j个1的摆放方案。

在一次操作[l,r]时,设下一个区间的左端点为nx,我们考虑由f[l-1,j]转移给f[nx-1,k]。因为当我们把[l,nx-1]中的元素固定后,由于左端点递增,所以这部分的元素已经不能再改变。

转移的时候可以枚举在[l,nx-1]中放多少个1,然后乘上一个组合数即可。

这样做看似是O(n^3)的,但分析一下不难发现复杂度其实是O(n^2)。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=3005;
const int MOD=1000000007;

int n,m,c

,a
,f

;
struct data{int l,r;}op
;
char str
;

bool cmp(data a,data b)
{
return a.l<b.l||a.l==b.l&&a.r<b.r;
}

void mod(int &x)
{
x-=x>=MOD?MOD:0;
}

void prepare()
{
c[0][0]=1;
for (int i=1;i<=n;i++)
{
c[i][0]=1;
for (int j=1;j<=i;j++) mod(c[i][j]=c[i-1][j]+c[i-1][j-1]);
}
}

int main()
{
scanf("%d%d",&n,&m);
prepare();
scanf("%s",str+1);
for (int i=1;i<=n;i++) a[i]=a[i-1]+str[i]-'0';
for (int i=1;i<=m;i++) scanf("%d%d",&op[i].l,&op[i].r);
sort(op+1,op+m+1,cmp);
int tmp=m,mx=0;
for (int i=1;i<=m;i++)
if (op[i].l==op[i+1].l||op[i].r<=mx) tmp--,op[i].l=n+1;
else mx=max(mx,op[i].r);
sort(op+1,op+m+1,cmp);
m=tmp;
f[op[1].l-1][a[op[1].l-1]]=1;
op[m+1].l=n+1;
for (int i=1;i<=m;i++)
{
int l=op[i].l,r=op[i].r,nx=op[i+1].l;
for (int j=0;j<l;j++)
{
if (!f[l-1][j]) continue;
int s=a[r]-j,p=max(0,s-(r-nx+1)),q=min(s,nx-l);
if (nx>r) {mod(f[nx-1][a[nx-1]]+=(LL)f[l-1][j]*c[r-l+1][s]%MOD);continue;}
if (p>q) continue;
for (int k=p;k<=q;k++) mod(f[nx-1][j+k]+=(LL)f[l-1][j]*c[nx-l][k]%MOD);
}
}
printf("%d",f
[a
]);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: