codeforces#326-D - Duff in Beach- dp+分块

http://codeforces.com/contest/588/problem/D 

题意给你  n, l and k (1 ≤ n, k, n × k ≤ 1^6 and 1 ≤ l ≤ 10^18).

给你n个数的数组a

然后  b数组就是 长度为l  由a数组不断重复拼接得到的数组

要你 从中选择一段合法子序列,求合法子序列的总数

条件如下:

1.这个序列的长度大于等于1,小于等于k

2.这个序列在每一个块中只能选择一个数,并且都必须选择在连续的块中 

3.这个序列是非递减的

题解:

tm原始数据排序后得到的数组

dp[i][j] 表示//dp[i][j]表示 tm[i]结尾的长度为j的方案数

考虑到n,k的范围,我们不能直接开数组。。就用个vector吧

//用vector来存dp, vector[i][j]表示 tm[i]结尾的长度为j的方案数

考虑转移方程

对与当前长度为j的方案数 时:

  以第i个数为结尾,长度为j 的方案数 =   (所有比tm[i]小的数) 的长度为j-1 的方案数之和 

for 一遍 o(n)就得到了长度为j 的方案数 了,然后 再对j  for一遍, o(n*k)得到所有方案数

****************************

然后  提供的最多可选择的块数  是len= l/n 块

我们选择 长度为 j  的子序列时, 因为每块选一个数,并且要选的必须是连续的块

显然 我们只有 len-j+1种 选法,并且每种选法都是合法的

所以

for (i=1;i<=l/n&&i<=k;i++)//因为必须选择连续的块,总共有l/n个块,所以可以选连续i个块 的方案数为 l/n-i+1
{
ans+=(l/n-i+1)%mod*sum[i];  //注意l/n-i+1要先mod一下,防止乘法溢出
ans%=mod;
}


最后 对 最后一段判断一下是否有 多出l%n,暴力判断一下就好了







#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std; 
__int64 tm[1000005];		//排序后的数组
__int64 sum[1000005];		//长度为i的总方案数
__int64 bb[1000005];		//原始数组
__int64 max(__int64 a,__int64 b)
{return a<b?b:a;}
const __int64 mod= 1000000007;
	
vector <__int64> sb[1000005];		//用vector来存dp, vector[i][j]表示 tm[i]结尾的长度为j的方案数
map<__int64,__int64>  up;			//映射原始数组和排序数组
int main()
{  
	__int64 n;
	__int64 i,j;
	__int64 l,k;
	scanf("%I64d%I64d%I64d",&n,&l,&k);
	for (i=1;i<=n;i++)
	{
		scanf("%I64d",&tm[i]);
		bb[i]=tm[i];
	}

	sort(tm+1,tm+1+n);
	for (i=1;i<=n;i++)	
		up[tm[i]]=i;


	for (i=1;i<=n;i++)
	{
		sb[i].push_back(0);//去掉第一个位置,从1开始
		sb[i].push_back(1);
	}

	for (j=2;j<=k;j++)
	{
		__int64 tot=1;
		for (i=1;i<=n;i++)
		{
			__int64 p=upper_bound(tm+1,tm+1+n,tm[i])-(tm+1);		//找到比tm[i]小的个数
			while(tot<=p)			
			{
				sum[j-1]+= sb[tot][j-1];//所有比tm[i]小的数的长度为j-1的方案,加上tm[i],就得到了以tm[i]结尾长度为j的方案数了 
				sum[j-1]%=mod;
				tot++;
			}
			sb[i].push_back(sum[j-1]);	//把得到的结果存入 tm[i]结尾长度为j的 dp位置中
		}
	}
	for (i=1;i<=n;i++)			//求一下sum[k]:长度为k的总方案数
	{
		sum[k]+=sb[i][k];
		sum[k]%=mod;
	} 
	__int64 ans=0;

	for (i=1;i<=l/n&&i<=k;i++)		//因为必须选择连续的块,总共有l/n个块,所以可以选连续i个块 的方案数为 l/n-i+1
	{
		ans+=(l/n-i+1)%mod*sum[i];  //注意l/n-i+1要先mod一下,防止乘法溢出
		ans%=mod;
	}	
	if (l%n)						//如果最后有剩余不完整的一小段,则对以该 数结尾的 所有长度小于k的方案数 都要加上一个 dp[i][len]: len=[1,k]
	{
		for (i=1;i<=l%n;i++)
		{
			for (j=1;j<=k&&j<=(l/n+1);j++)
			{
				__int64 idx= up[bb[i]];
				ans+=sb[idx][j];
				ans%=mod;
			}
		}
	}
	printf("%I64d\n",ans);
 

	
	return 0;
	
}


你可能感兴趣的:(codeforces#326-D - Duff in Beach- dp+分块)