zoukankan      html  css  js  c++  java
  • 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(); 
    }
    
  • 相关阅读:
    java面向对象小总结
    eclipce 快捷键
    linux中查找和过滤的用法:find,grep
    shell脚本
    条形码和二维码
    Json简介
    牛客练习
    输入流操作
    Java编码
    maven入门
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15775729.html
Copyright © 2011-2022 走看看