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();
}