加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

波动数列 (蓝桥杯编程题)(超级好的一个 DP 类型的题)

(2016-02-19 16:34:02)
标签:

it

蓝桥杯

dp

好题

分类: ACM
描述:
观察这个数列: 1 3 0 2 -1 1 -2 ...    这个数列中后一项总是比前一项增加2或者减少3。
    想知道长度为 n 和为 s 而且后一项总是比前一项增加a或者减少b的整数数列可能有多少种呢?

【数据格式】
    输入的第一行包含四个整数 n s a b,含义如前面说述。
    输出一行,包含一个整数,表示满足条件的方案数。由于这个数很大,请输出方案数除以100000007的余数。

例如,输入:
4 10 2 3
程序应该输出:
2
【样例说明】
这两个数列分别是2 4 1 3和7 4 1 -2。

【数据规模与约定】
对于10%的数据,1<=n<=5,0<=s<=5,1<=a,b<=5;
对于30%的数据,1<=n<=30,0<=s<=30,1<=a,b<=30;
对于50%的数据,1<=n<=50,0<=s<=50,1<=a,b<=50;
对于70%的数据,1<=n<=100,0<=s<=500,1<=a, b<=50;
对于100%的数据,1<=n<=1000,-1,000,000,000<=s<=1,000,000,000,1<=a, b<=1,000,000。

分析:
设对于任意一个合法的起始数字 x,(根据题意,只要 x 为整数即可),那么在前一个“基数”的基础上,走下一步时,无非俩种选择,要么 +a,要么 -b,由基数的俩种可能变化,有导出了新的基数,然后对于每一个新的基数,同样俩种可能的变化选择……显然,会出现一个树形的解空间结构。或者,对于每一个状态节点的+/-俩种选择,像极了背包问题对于每个物品的 0-1 取舍,很直接的就可以想到搜索去求解。但是根据数据规模,搜索的时间复杂度为 2^n , 对于 n<50 的规模还算可以,但题目的数据提示,有一半的数据 n 值都是超过 50 的,显然搜索是不行的……
看了解题报告,原来是 DP,但这个题 DP 的很巧妙,理解之后觉得,它其实是从另一个角度去看问题,没有直接从各个元素值 && 和的角度入手。
不妨设起始数字为 x ,并且+a/-b 的变化统一标记为 p。那么显然,对于 n 项的和,应该为 x+(x+p)+(x+2p)+……+(x+(n-1)p) = s。进一步化简有 : nx+((n-1)n/2)p = s。显然,对于任意未知的定值 x,操作 a&&b 的次数和是一定的,次数和为 (n-1)n/2。且根据上边的通式,操作次数权值的合法序列为 1,2,3,…… (n-1),即第 i+1 个数是在基数 x 的基础上变化  i 倍的 a 或者 i 倍的-b,其变化倍数刚好是权值序列第 i 个数的值。若最终结果中一共有 m 倍的 a,那么 b 的变换倍数显然也就确定了。但是……很明显,对于同一个 a 的总变化倍数 m,合法序列不见得只是 1 种显然,结果的次数不知取决于总的变化次数,还取决于变化序列-------->  DP 也就是站在序列的角度上 DP 的,对于权值序列 1 , 2 , 3 ……(n-1)进行 DP
设函数 dp(i,j)表示序列的前 i 项中 a 的次数为 j 时的方案种数。(这个定义为 前 i 项 a 的次数和为 j,显然,与序列顺序是有关的,即 dp 记录的方案数时与序列有关的,定位到了序列--->显然,只要确定了序列中 a 的各项的选择的分布顺序, b 也就确定了,即为一种合法的变化序列)。
根据 dp 函数的定义,显然有边界条件: dp(i,0) = 1  (i != 0)(前 i 项 a 的次数和为 0 , 即 all b项,只有 1 种可能); dp(0,j)= 0 (j != 0)(前 0 项 a 的次数和非 0 , 显然是不可能的)
一般的, 若 i > j , 即该项 a 的次数权值 i > 和次数 j,显然该项是不包含在统计中的,so  dp(i,j) = dp(i-1,j);若 i <= j , 显然该项可以包含在和项 j 中也可以不包含在其中,so dp(i,j) = dp(i-1,j)+dp(i-1,j-i)。
----> DP 实际上是对有序序列进行的。有序序列的合法方案种数。
具体实现时,由上分析,需要记录表 red(x,y),x 即前 i 项,范围 [ 0 , n-1 ] , y 即次数和,范围为 [ 0 , (n-1)*n/2 ] .  需要注意的是,列项的和并不是对若干项元素值的进行计和,而是次数权值序列中 a 的次数进行计和。根据 n 值上限,次数计和最大值不超过 1e6,反之对若干项元素值计和,1e9的数量级,根本就开不下这么大的数组。
同时,n 值虽为 1e3的数量级,但整体空间也是很大的。根据状态转移方程,(i,x)下的状态仅取决于 (i-1,x)下的状态,这个状态转移方程像极了 0-1 背包的 DP 方程,是完全可以状态压缩的,用滚动数组法进行状态压缩

同时,最终把表 red 求解完后,关于最终结果的得出:
有方程式 :nx+((n-1)n/2)p = s,可以得出 nx= s-(((n-1)n/2)p) , 根据题意,序列中各个项都应为整数,由上式有 :  s-(((n-1)n/2)p) = nx, nx 必为整数,即 s-(((n-1)n/2)p) 比能够整除 n ,故 求完状态记录表后,前 (n-1)项中,a 的合法次数值可能是 [ 0 , (n-1)*n/2 ] 中的任意一个,还需要逐个遍历,进行检查,由 s-(((n-1)n/2)p) 检测其是否可整除 n 来确定要不要收集该表项中的次数 t,对于合法项,对其次数累计求和即可

同时,类型小总结,很多看似很像搜索的题甚至搜索就可以解决,只是因为数据规模太大,时间上吼不住时,其实大都是可以 DP 求解的。

有序序列数目的计数问题 --->  DP.

示例代码:
const int Max = 100000007;
const int MaxSum = 1e6;

int n , s , a , b;
int number[2][MaxSum];

int dp(int maxTimes){ 
int i , j;
// initial the boundry
for(j = 1 ; j <= maxTimes ; ++j){
number[0][j] = 0;
}
number[0][0] = 1;
int cur = 1;
for(i = 1 ; i < n ; ++i){
number[cur][0] = 1;
for(j = 1 ; j < i ; ++j){
number[cur][j] = number[1-cur][j];
}
for(j = i ; j <= maxTimes ; ++j){
number[cur][j] = number[1-cur][j]+number[1-cur][j-i];
}
cur = 1-cur;
}
cur = 1-cur;
for(j = 0 , i = 0 ; j <= maxTimes ; ++j){
if((s-j*a-maxTimes*b+j*b)%n == 0){
i += number[cur][j];
i %= Max;
}
}
return i;
}

int main(int argc, char** argv) 
{
scanf("%d%d%d%d" , &n , &s , &a , &b);
int maxTimes = (n-1)*n/2;
cout << dp(maxTimes) << endl;
return 0;
}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有