P1077 [NOIP2012 普及组] 摆花https://www.luogu.com.cn/problem/P1077
题目描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n 种花,从 1到 n 标号。为了在门口展出更多种花,规定第 i 种花不能超过 ai 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
输入格式
第一行包含两个正整数 n 和 m,中间用一个空格隔开。
第二行有 n 个整数,每两个整数之间用一个空格隔开,依次表示 a1,a2,⋯,an。
输出格式
一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对 10^6+7 取模的结果。
输入输出样例
输入 #1复制
2 4 3 2
输出 #1复制
2
说明/提示
【数据范围】
对于 20% 数据,有 0<n≤8,0<m≤8,0≤ai≤8。
对于 50% 数据,有 0<n≤20,0<m≤20,0≤ai≤20。
对于 100% 数据,有 0<n≤100,0<m≤100,0≤ai≤100。
思路:题意--{b1,b2,b3...bn}(0<=bi<=ai)的和等于m。
- dp[i][k]表示对于前i种花,摆k盆的方案之和
- 状态转移方程:dp[i][k]等于dp[i-1][k-a[i]]~dp[i-1][k]之和。这是因为,相比i-1,i多了一种花。而这种花摆放的数量可以是0~ai.假设摆放第i花 j 盆总数为k,方案数=dp[i-1][k-j].j从0~ai的所有方案数之和即为dp[i][k]
- dp数组的初始化:当m=0时,不能摆花,方案唯一且为1。由于dp由上一排前k个元素推导而来,因此只用初始化dp[0][0]=1.
- 遍历顺序:由于dp由上一排前k个元素推导而来,因此遍历顺序为从上到下,从左到右。
代码实现
#include<bits/stdc++.h>
using namespace std;
int dp[105][105];
int mod=1000000+7;
int a[105];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
dp[0][0]=1;//dp由上一排的左方元素推导而来
for(int i=1;i<=n;i++)
{
for(int k=0;k<=m;k++)
{
for(int j=0;j<=a[i];j++)
{
if(j<=k)
{
dp[i][k]=(dp[i][k]+dp[i-1][k-j])%mod;
}
}
}
}
printf("%d",dp[n][m]);
return 0;
}
//#include<bits/stdc++.h>
//using namespace std;
//int mod=1000000+7;
//int a[105];
//int ans[105][105];
//int n,m;
//int dfs(int i,int sum)
//{
// if(sum==m) return 1;
// if(sum>m) return 0;
// if(i==n+1) return 0;
// if(ans[i][sum]) return ans[i][sum];
// for(int j=0;j<=a[i];j++)
// {
// ans[i][sum]=(ans[i][sum]+dfs(i+1,sum+j))%mod;
// }
// return ans[i][sum];
//}
//int main()
//{
//
// scanf("%d%d",&n,&m);
// for(int i=1;i<=n;i++)
// {
// scanf("%d",&a[i]);
// }
// printf("%d",dfs(1,0));
// return 0;
//}
附记忆化深搜
P1439 【模板】最长公共子序列https://www.luogu.com.cn/problem/P1439
题目描述
给出 1,2,…,n 的两个排列 P1 和 P2 ,求它们的最长公共子序列。
输入格式
第一行是一个数 n。
接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例
输入 #1复制
5 3 2 1 4 5 1 2 3 4 5
输出 #1复制
3
说明/提示
- 对于 50% 的数据, n≤10^3;
- 对于 100% 的数据, n≤10^5。
思路:
- 由于输入的数没有重复,不会冲突,可以利用映射,将第2行输入的数a通过映射转化为它的下标i switch[a]=i;
- 通过1的映射关系switch,将第3行输入的数转换一下。
- 转换后,第2行数为1~n的上升序列,因此第3行数的上升子序列一定为他们的公共部分。
这样问题就变成了:求最长上升子序列 https://www.luogu.com.cn/problem/B3637
但是由于题目数据量为10^5, n^2的最长上升子序列会tle。
模拟栈 栈中元素数量,即为最长公共子序列。
- 比栈顶元素大,该元素入栈
- 比栈顶元素小,找到栈中第一个比该元素大的数,替换。
这样的栈,始终单调递增,因此可以二分搜索进行第2步操作。
第二步操作:
- 替换不会增加元素数量
- 操作意义在于:假设进行了一定次数的替换,甚至栈顶也被替换,这样有利于栈中元素个数的增加。
划重点
代码实现
#include<bits/stdc++.h>
using namespace std;
int Switch[100005],b[100005];
int dp[100005];
int mid_search(int l,int r,int x)
{
while(l<r)
{
int mid=(l+r)/2;
if(dp[mid]>x) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
int n;
scanf("%d",&n);
int a;
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
Switch[a]=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
b[i]=Switch[b[i]];
}
int top=0;
for(int i=1;i<=n;i++)
{
if(b[i]>dp[top])
{
dp[++top]=b[i];
}
else
{
int pos=mid_search(0,top,b[i]);
dp[pos]=min(b[i],dp[pos]);
}
}
printf("%d",top);
return 0;
}
//#include<bits/stdc++.h>
//using namespace std;
//int dp[20005][20005];
//int a[100005],b[100005];
//int main()
//{
// int n;
// scanf("%d",&n);
// for(int i=0;i<n;i++)
// {
// scanf("%d",&a[i]);
// }
// for(int i=0;i<n;i++)
// {
// scanf("%d",&b[i]);
// }
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n;j++)
// {
// dp[i][j]=1;
// }
// }
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n;j++)
// {
// if(a[i-1]!=b[j-1]) dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
// if(a[i-1]==b[j-1])
// {
// dp[i][j]=dp[i-1][j-1]+1;
// }
// }
// }
// printf("%d",dp[n][n]);
// return 0;
//}
附n^2的朴素最长公共子序列mle+tle