浇花 差分数组 区间DP
来源:互联网 发布:vue.js 提交表单 ajax 编辑:程序博客网 时间:2024/04/29 05:35
NKOJ 3051 浇花
问题描述
n 个非负整数排成一行,每个数值为Ai,数的位置不可改变。需要把所有的数都恰好等
于h。可进行的操作是:对任意长度的区间[i,j]中的每个数都加1,i 和j 也任选,但要求每
个数只能作为一次区间的起点,也只能作为一次区间的终点。也即是说: 对任意的两个区
间[l1, r1] 和[l2, r2], 要求: l1≠l2 并且r1 ≠ r2.
请问有多少种不同的方式,使所有的数都等于h.输出答案模1000000007 (10^9+7)后的
余数。
两种方式被认为不同,只要两种方式所实施的操作的区间集合中,有一个区间不同即可.
输入格式
第1 行:2 个整数n, h (1 ≤ n, h ≤ 2000)
接下来n 行,每行1 个整数,表示Ai (1≤Ai≤2000)
30%的数据,n, h <= 30
100%的数据,n, h <= 2000
输出格式
第1 行:1 个整数,表示答案
样例输入
Sample1:
3 2
1 1 1
Sample2:
5 1
1 1 1 1 1
样例输出
Sample1:
4
Sample2:
1
方法一:区间DP
这种做法,考试时的题解说得非常详细了,这里直接复制下来了:
类似于括号dp的讨论方式,讨论i的左边,选哪个数字作为区间的起点,更新i的值dp[i][k]表示从左往右讨论到第i个数字,i的左边有k个数字还未被用过(被当做区间的左起点), 的方案数。分两种情况讨论:情况1:i被别人更新(因为i前面的k个数,任选一个为区间起点,都可更新到i): 若a[i]+k==h 则dp[i][k]=dp[i+1][k-1]*k+dp[i+1][k] 说明,条件a[i]+k==h,因为i左边有k个数字还没用过,那么以这k个数字作为区间左起点可以操作k次,每次都可以更新到i,更新k次,恰好就能使a[i]变成h。 现在对于i而言,有两种选择, 使用i或者不使用i。 若用i作为区间右端点,因为i只能当一次区间终点,所以只能从前k个中选一个来与它配对,故有k种方案,k个数中i选了一个,对于i+1它左边就只有k-1个未使用的数了,数量总数为k*dp[i+1][k-1] 。 注意,这里i不能再作为区间的左端点了,这样的话会导致i被多更新一次,高度变成h+1 若不用i作为区间端点,则方案数为dp[i+1][k]情况2:i作为区间起点去更新别人 若a[i]+k+1=h则dp[i][k]=dp[i+1][k]*(k+1)+dp[i+1][k+1] 说明,因为i前面有k个数未被当做左起点使用,全部操作都只能把a[i]更新到h-1这个高度,那么i号点必须自己作为某区间的左起点更新一次,在更新这个区间的同时把自己的高度也更新1,达到h。 这样,对于下一个数i+1而言,算上i号点,它左侧有k+1个点可选做区间左端点,任选一个选后剩下k个点,状态dp[i+1][k] 若不用i作为区间左端点,则方案数为dp[i+1][k+1]时间复杂度O(n^2),实现时采用记忆化搜索比较方便。
代码:
#include<stdio.h>#include<cstring>#define ll long longconst ll mod=1e9+7,MAXN=2005;ll f[MAXN][MAXN];int N,H,A[MAXN];ll DFS(int i,int k){ if(~f[i][k])return f[i][k]; if(i==N+1) { if(!k)return 1; return 0; } if(A[i]+k==H)return f[i][k]=(DFS(i+1,k-1)*k+DFS(i+1,k))%mod; if(A[i]+k==H-1)return f[i][k]=(DFS(i+1,k)*(k+1)+DFS(i+1,k+1))%mod; return f[i][k]=0;}int main(){ int i; scanf("%d%d",&N,&H); for(i=1;i<=N;i++) { scanf("%d",&A[i]); if(A[i]>H)return puts("0"),0; } memset(f,-1,sizeof(f)); printf("%lld",DFS(1,0));}
方法二:差分数组
区间整体加减某个数,这是差分数组的强烈信号。如果把
这里有个重要的结论:
如果存在解(方案数不为0),那么
B 的值只能在0,1,-1三个数当中取。
说到这里,首先讲完无解的特判:
满足以下两个条件中的任意一个即无解:
1.存在
Ai>H 。
2.存在Bi ,满足Bi≠1,0,−1
下面解释为什么有解时
不妨逆向考虑,如果有解,那么最后差分数组的情况肯定全是0。现在要把这个差分数组变成原来的样子,这样操作就由区间整体减变成了区间整体加。我们知道,如果对区间[x,y]加1,那么在差分数组里的体现就是
下面考虑如何统计答案。还是用的方法一的括号配对的思路:
用cnt表示当前位置的左边有多少
1.若当前
2.若当前
3.若当前
1)当前位置既不是操作过的区间起点,也不是操作过的区间终点。这种情况对方案总数没有影响。
2)当前位置既做过一次操作过的区间起点,又做过一次操作过的区间终点。做过一次区间起点,所以cnt++;做过一次区间终点,所以可以和cnt个区间起点配对,方案总数乘上cnt。之后cnt–。这种情况的最终结果就是使方案数乘上(cnt+1)。
代码:
#include<stdio.h>#define ll long longconst ll mod=1e9+7,MAXN=2005;ll Ans=1,cnt,A[MAXN],B[MAXN],N,H,x;int main(){ int i; scanf("%lld%lld",&N,&H); for(i=1;i<=N;i++) { scanf("%lld",&x); A[i]=H-x; if(A[i]<0)return puts("0"),0; } for(i=1;i<=N+1;i++)B[i]=A[i]-A[i-1]; for(i=1;i<=N+1;i++) { if(B[i]==1)cnt++; else if(B[i]==0)Ans=Ans*(cnt+1)%mod; else if(B[i]==-1)Ans=Ans*cnt%mod,cnt--; else return puts("0"),0; } printf("%lld",Ans);}
- 浇花 差分数组 区间DP
- NKOJ 3051 浇花 (差分数组/区间DP)
- 树状数组 区间修改 差分
- HDU6170【DP+树状数组+差分维护】
- DP/搜索-分数组为两个部分-最小差
- NKOJ 2522 Sandy的卡片(差分数组+DP)
- 利用差分实现的树状数组区间修改 区间求和
- 差分数组
- 差分数组概述
- 对等差分数组
- 差分数组
- 差分数组
- 差分数组
- 划分dp,区间差最小
- vijos区间(差分约束)
- 区间(差分约束模板)
- Intervals 【区间差分约束】基础
- POJ 1201 Intervals (区间差分约束)
- 二级C考试的重要知识点
- THINKING IN JAVA中吸血鬼数字题目的答案,稍作修改
- iOS github账号添加 add an ssh key
- faster rcnn配置成功
- Java终止线程的四种方法
- 浇花 差分数组 区间DP
- 第九课、C存储类
- Ubuntu使用doxygen将源码生成调用关系图
- 导航
- 解决 python中加上中文注释报错
- BZOJ 1499 [NOI2005]瑰丽华尔兹 动态规划(+单调队列)
- win10激活
- js语言扩展之trim
- php+iis6.0配置故障