题面
https://www.luogu.com.cn/problem/UVA12983
题意翻译
本题题意就是让你码个程序,然后这个程序是要你在一个数字序列中找到长度为m的严格上升子序列的个数(注意不是子串),然后答案对 10^9 + 7109+7 取模。
什么意思呢?举个例子(当然不是题目中的例子):
首先是n=5,m=3。 然后是一个数字序列: 1 3 4 2 5
那么符合条件的序列就有: 1 3 4 1 2 5 1 3 5 1 4 5 3 4 5
于是最终答案就是5 。 这样题意应该就比较清晰了
数据范围:1<=M<=N<=1000;1<=ai<=1e9;1<=T<=100;mod=1e9+7;
简介
来一发简单易懂的题解,也是本蒟蒻的第一篇题解。首先这道题,很明显就是dp。
用dp[i][j]表示以第i个数结尾,长度为j的严格递增序列的长度。
很容易可以得出
dp[i][j]=sum{dp[k][j-1]}1<=k<=i-1,且a[k]<a[i]
但是考虑到一个个找效率不高,于是考虑用树状数组优化,记树状数组为
tree[i][j],表示长度为i且末尾分别的值为j-( j & ( -j ) )+1 ~ j 的严格递增序列的个数之和
其中(j&(-j))表示二进制数最右边的1所表示的数值
即 6(10)=110(2),那么6&(-6)=10(2)=2
编完之后可以发现,dp[ i ][ j ]其实是无用的(代码中有解释)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int MOD=1e9+7;
int n,m,tree[1001][1001],pos;
struct date {
int num;//原序列中的位置
int val;//原数值
int big;//重新赋值后的数值
} a[3005];
bool cmp1(date x,date y) {
return x.val<y.val;//按原数值从小到大排序
}
bool cmp2(date x,date y) {
return x.num<y.num;//排回原数列的位置.
}
void change(int *z,int x,int y) {
while(x<=n) {
z[x]+=y;
z[x]%=MOD;//记得模,我因为没mod错了好几次
x+=(x&(-x));
}
}//改变值
int sum(int *z,int x) {
int ans=0;
while(x) {
ans+=z[x];
ans%=MOD;
x^=(x&(-x));
}
return ans%MOD;
}//求dp[1][k]~dp[x][k]之和.
void solve() {
//memset(dp,-127/3,dp);
memset(tree,0,sizeof(tree));
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)scanf("%d",&a[i].val),a[i].num=i;
sort(a+1,a+1+n,cmp1);
a[1].big=1;
int j=1;
for(int i=2; i<=n; i++) {
if(a[i-1].val!=a[i].val)j++;
a[i].big=j;
}//重新赋值,不然数组开不了那么大.
sort(a+1,a+1+n,cmp2);
for(int i=1; i<=n; i++) {
//dp[i][1]=1;
change(tree[1],a[i].big,1);//第一个做特殊处理
for(int j=m; j>1; j--) {
int k=sum(tree[j-1],a[i].big-1);
if(k) {
//dp[i][j]=k;
change(tree[j],a[i].big,k);
}//如果k非0说明前面存在j-1长度的,则转移.
}
}
pos++;//计数
printf("Case #%d: %d\n",pos,sum(tree[m],n)%MOD);
//看到这里大家应该能发现实际我们存的dp数组没啥用,所以
//可以省略掉
}
int main() {
int t;
scanf("%d",&t);
while(t--) {
solve();
}
return 0;
}
后言
有个小小的反抄袭操作,希望大家能自己理解一下,诚信做题,对树状数组不太了解的同学可以先去看看树状数组模板题。