这是一道经典dp 的题目!
题目意思大致就是: 给出一个序列,问你任意删除m个字符,有不同种方案? 每个数字的范围在k之内。
我们可以设 dp[i][j] 表示前i个元素删除j个元素后的方案数 很容易推出dp方程:
dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
意思就是 前i个元素删j个=要么我这个i位置的数不删,那么就从前i-1个数中删j个,或者我这个i位置给他删掉,那么就从前i-1个数中删j-1个,(因为i位置被删了,所以只要删j-1个就够了)
但是会出现重复的情况,比如 下面这个例子 1 2 4 5 6 7 4
比如我们i跑到了第7个位置,此时a[7]=4.我们可以发现,如果删掉4 5 6 7或者删掉5 6 7 4.那么剩下的都为1 2 4. 这样的方案就发生重复了。
然后刚刚上面计算的是所以情况(包含重复的) ,利用容斥定理剪掉重复的部分就是我们的答案 。。
我们可以发现,第一个4和第二个4之间的数是一定要删完的,然后才会产生重复,重复的方案就是=dp[pos-1][j-(i-pos)] pos表示前面第一个和a[i]相同的数的位置。我们删掉了i-pos个数,j-(i-pos)表示剩下还需要删的,也就是说,在前pos-1个数中,删j-(i-pos)个数,就是重复的。
AC代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <queue>
#include <deque>
#include <set>
#include <stack>
#include <cctype>
#include <cmath>
#include <cassert>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}
const int N=1e5+100;
const int M=15;
int dp[N][M];//dp[i][j]表示前i个数删掉j个数的方案
int n,m,k2;
int a[N];
int main()
{
while(~scanf("%d%d%d",&n,&m,&k2))
{
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++)
{
dp[i][0]=dp[i][i]=1;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
int pos=0;
for(int k=i-1;k>=1;k--)
{
if(a[i]==a[k])
{
pos=k;
break;
}
}
if(i-pos<=j&&pos-1>=0)dp[i][j]=(dp[i][j]-dp[pos-1][j-(i-pos)]+mod)%mod;
}
}
printf("%d\n",dp[n][m]%mod);
}
return 0;
}

京公网安备 11010502036488号