P3970 [TJOI2014] 上升子序列

Link

这题在省选算是简单的吧。

看了一圈题解,好像大家都去重了,写一个不用去重的抽象做法。

先离散化,设 dpidp_i 表示ii 这个数为结尾的子序列最多有多少个。

然后直接枚举 [1,i1][1,i-1] 的数转移即可,发现可以线段树优化到单次 O(logn)O(\log n)

但是题目要求不重复。

一个重要的观察:对于两个相同的数 aja_jaia_i,其中 j<ij<i,以 ii 这个位置为结尾的子序列一定是要不少于以 jj 这个位置为结尾的子序列个数。

原因显然,因为以 jj 这个位置为的所有子序列去掉 aja_j,完全可以换上 aia_i 代替,相当于 aia_i 的答案覆盖了 aja_j,所以是不少于。

既然 aia_i 可以覆盖到 aja_j 的答案,由此可以得出一个结论,就是对于 aia_i 这个数,它一定覆盖了前面等于他的所有数的答案。对于 aia_i 后面的数,我们只用关心 aia_i 即可。

那就解决了重复的问题了,对于当前的数,我们只用统计当前前面出现过且小于的数的答案即可,并且更新当前数对应的 dpdp 值。可以直接更新的原因是具有单调性。

注意初始化要为 11,就当长度为 11 的也算进去,为了后面统计的时候有值。最后再减去即可。

警钟敲烂:在取模的情况下不能用取最大值或最小值,这都是不准的。

#include<bits/stdc++.h>
using namespace std;
const int N =1e6+10;
const int mod=1e9+7;
#define int long long 
struct node{
	int val,id;
}a[N];
int dp[N],lst[N],c[N],d[N<<1];
bool cmp(node a,node b){
	return a.val<b.val;
}
int Query(int l,int r,int s,int t,int p){
	if(l<=s&&t<=r)	return d[p];
	int mid=(s+t)>>1,sum=0;
	if(l<=mid)	sum+=Query(l,r,s,mid,p<<1),sum%=mod;
	if(r>mid)	sum+=Query(l,r,mid+1,t,p<<1|1),sum%=mod;
	return sum;	
}
void update(int l,int r,int change,int s,int t,int p){
	if(l<=s&&t<=r){
		d[p]+=change,d[p]%=mod;
		return ;
	}
	int mid=(s+t)>>1;
	if(l<=mid)	update(l,r,change,s,mid,p<<1);
	if(r>mid)	update(l,r,change,mid+1,t,p<<1|1);
	d[p]=(d[p<<1]+d[p<<1|1])%mod;
	return ;
}
signed main()
{
//		freopen("1.in","r",stdin);
//	freopen("ans1.out","w",stdout);
	int n,node=1;
	cin>>n;
	for(int i=1;i<=n;i++)	cin>>a[i].val,a[i].id=i;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		if(i==1)	node++;
		else if(a[i].val!=a[i-1].val)	node++;
		c[a[i].id]=node;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int now=Query(1,c[i]-1,1,n,1);
		update(c[i],c[i],now+1-dp[c[i]],1,n,1),dp[c[i]]=now+1,dp[c[i]]%=mod;	
	}
	for(int i=2;i<=node;i++)	
		ans+=dp[i]-1,ans%=mod;
	cout<<ans<<endl;
	return 0;
}
posted @   June_Failure  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示