CF1621G Weighted Increasing Subsequences

一、题目

点此看题

二、解法

我们先对原序列离散化,相同权值的元素后面的小,显然这个题是拿来给你算贡献的,设 \(y\) 表示最大满足 \(a_y>a_x\) 的下标,考虑位置 \(x\) 的贡献是包含 \(x\) 的上升子序列个数,并且序列结尾小于 \(y\)

直接算复杂度起飞,优化需要考察点 \(y\) 更深入的性质,\((y,n]\) 这一段区间的所有权值都比 \(a_x\) 要小,那么它们都不可能作为序列的结尾,利用正难则反的思想我们可以计算以 \(y\) 结尾包含 \(x\) 的上升子序列个数,再拿总方案数减去它即可,这步转化的作用是固定了结尾。

\(y\) 结尾包含 \(x\) 的子序列个数相当于以 \(x\) 结尾的子序列个数乘上以 \(x\) 开头 \(y\) 结尾的子序列个数,第一部分可以简单预处理出来,考虑如何快速计算第二部分。

一种优化的思想是只保留有用的点,我们固定 \(y\),设 \(a_z\) 表示 \(z>y\) 的最大值,那么有用的 \(x\) 一定满足 \(a_z<a_x<a_y\),我们考虑把可能的后缀最大值排成数组,那么每个 \(a_x\) 一定会只属于其中的一个元素。那么我们枚举 \(y\) 之后可以暴力找出所有 \(x\),然后暴力进行最长上升子序列的 \(dp\) 操作,时间复杂度 \(O(n\log n)\)

三、总结

计数问题中把范围问题转化成单点问题更好,本题的转化用到了正难则反法。

只保留有用的点再暴力计算是重要的优化方式,在树上的一些问题中我们也常常看见它的身影。其实本题的精髓在于枚举 \(y\)\(x\) 创造了限制,这样点数就很有限了,所以枚举是创造限制的重要方法

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,ans,a[M],b[M],id[M],f[M],g[M],h[M],s[M];
vector<int> v[M];
int cmp(int x,int y)
{
	if(a[x]!=a[y]) return a[x]<a[y];
	return x>y;
}
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int c)
{
	for(int i=x;i<=n;i+=lowbit(i))
		b[i]=(b[i]+c)%MOD;
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=lowbit(i))
		r=(r+b[i])%MOD;
	return r; 
}
void work()
{
	n=read();m=ans=0;
	for(int i=1;i<=n;i++) a[i]=read(),id[i]=i;
	sort(id+1,id+1+n,cmp);
	for(int i=1;i<=n;i++) a[id[i]]=i,b[i]=0;
	for(int i=1;i<=n;i++)
	{
		f[i]=ask(a[i])+1;
		add(a[i],f[i]);
	}
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=n;i>=1;i--)
	{
		g[i]=ask(n-a[i]+1)+1;
		add(n-a[i]+1,g[i]); 
	}
	for(int i=1;i<=n;i++) b[i]=0,v[i].clear();
	for(int i=n;i>=1;i--)
		if(a[i]>a[s[m]]) s[++m]=i;
	for(int i=n;i>=1;i--)
	{
		int l=1,r=m,x=1;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(a[i]<=a[s[mid]]) r=mid-1,x=mid;
			else l=mid+1;
		}
		if(i!=s[x]) v[x].push_back(i);
	}
	for(int i=1;i<=m;i++)
	{
		add(n-a[s[i]]+1,h[s[i]]=1);
		for(int x:v[i])
		{
			h[x]=ask(n-a[x]+1);
			add(n-a[x]+1,h[x]);
		}
		for(int x:v[i])
			add(n-a[x]+1,MOD-h[x]);
		add(n-a[s[i]]+1,MOD-1);
	}
	for(int i=1;i<=n;i++)
		ans=(ans+(g[i]-h[i]+MOD)%MOD*f[i])%MOD;
	printf("%lld\n",ans);
}
signed main()
{
	T=read();
	while(T--) work(); 
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15775729.html