牛客周赛ROUND38

E题

链接:https://ac.nowcoder.com/acm/contest/78292/E
来源:牛客网

小苯非常喜欢等比数列。有一天他得到了一个长为 \(n\) 的数组 \(a\),他想从里面选一些数字使得被选中的数字构成一个等比数列,请问他最多可以选择多少个数字呢

输入包含两行。

第一行一个正整数 \(n\ (1\leq n \leq 2 \times 10^5)\),表示数组 \(a\) 的长度。
第二行 \(n\) 个正整数 \(a_i \ (1 \leq a_i \leq 2 \times 10^5)\),表示数组 \(a\) 的元素。

solution:比较经典的不同范围不同做法,由于公比过大的时候会乘几次就结束,所以我们对此暴力。对于公比小的部分进行dp递推

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int N = 2e5 + 5;
inline void solve()
{
    int n; cin >> n;
    vector<int> a(n + 5);
    vector<int> st(N, 0);
    for(int i=1; i<=n; i++)
        cin >> a[i], st[a[i]] = 1;
    sort(a.begin()+1, a.begin()+1+n);
    int ans = 1;
    vector<vector<int>> dp(N, vector<int>(101, 0));//dp[i][j]:以a[i]结尾的, 公比为j的等比数列的最大长度
    
    //100n
    for(int i=1; i<=n; i++) {
        dp[a[i]][1] ++;
        ans = max(ans, dp[a[i]][1]);
        for(int j=2; j<=100; j++) {//公比
            if(a[i] % j == 0) {
                if(st[a[i] / j]) dp[a[i]][j] = 2;//如果只选了这两个
                dp[a[i]][j] = max(dp[a[i]][j], dp[a[i] / j][j] + 1);
                ans = max(ans, dp[a[i]][j]);
            }
        }
    }
    //分块
    for(ll i=100; i<=2e5; i++) {//公比>100, 至多1~3次
    //实际复杂度只有O(n)
        for(ll j=1; j*i<=2e5; j++) {//首项
            if(!st[j]) continue;
            if(!st[j*i]) {
            	//考虑有没有第二项
                ans = max(ans, 1); continue;
            } 
            if(j*i*i > 2e5 || !st[j*i*i]) {
            	//考虑有没有第三项
                ans = max(ans, 2); continue;
            }
            ans = max(ans, 3);
            break;
        }
    }
    cout << ans << endl;
}

G:双指针维护逆序对

题目

链接:https://ac.nowcoder.com/acm/contest/78292/G
来源:牛客网

小红有一个长度为 \(n\) 的数组 \(a\),他想从 \(a\) 中删除一段区间,但又要使得剩余的部分拼起来组成的新数组满足:逆序对个数不少于 \(k\)

她想知道有多少种删除方案,请你帮帮她吧。
注:空数组逆序对数量为0。

solution:首先本题就是双指针,只不过维护的是逆序对,比常规双指针增加难度

我们考虑固定R。对于L,由于是删除区间,左指针右移表示删除区间缩小,逆序对数量增加。
所以我们对于每一个r需要找到极限l,l满足恰好>=k。考虑每次移动l都增加了多少逆序对,我们计算具体贡献

  1. 增加的是R后面比a[l]小的元素,
  2. 还有就是L前面a[l]大的
  • 对于2的贡献我们只需要移动l的时候在线维护
  • 对于1的贡献由于指针是从左往右移动,所以我们只有提前维护后缀的值域树状数组,跟着r移动的时候在线删除维护,这样才可以
  • 每次删除右端点的元素减去贡献计算同理
int n, m;
int a[N];
//首先本题就是双指针,只不过维护的是逆序对,比常规双指针增加难度
//我们考虑固定R。对于L,由于是删除区间,左指针右移表示删除区间
//缩小,逆序对数量增加。
//所以我们对于每一个r需要找到极限l,l满足恰好>=k。
//每次移动l都增加了多少逆序对,我们计算具体贡献

//1增加的是R后面比a[l]小的元素,
//2.还有就是L前面a[l]大的


//对于2的贡献我们只需要移动l的时候在线维护
//对于1的贡献由于指针是从左往右移动,所以我们只有提前维护后缀的
//值域树状数组,跟着r移动的时候在线删除维护,这样才可以
template <typename T>
struct Fenwick {
    int n;
    std::vector<T> a;
    
    Fenwick(int n_ = 0) {
        init(n_);
    }
    
    void init(int n_) {
        n = n_;
        a.assign(n, T{});
/*
T{}是一种初始化T类型对象的方式,它使用了花括号初始化列表进行数值初始化。
对于大多数内置数据类型(如整数、浮点数),这将初始化为0。
*/

    }
    
    void add(int x, const T &v) {
        for (int i = x; i <=n-1; i += i & -i) {
            a[i] = a[i] + v;
        }
    }
    
    T sum(int x) {
        T ans{};
        for (int i = x; i > 0; i -= i & -i) {
            ans = ans + a[i];
        }
        return ans;
    }
    
    T rangeSum(int l, int r) {//要传入l-1
    //待优化:直接给个vector记录前缀和
        return sum(r) - sum(l);
    }
    
    int select(const T &k) {//寻找最后一个使得前缀和小于等于 k 的位置。
        int x = 0;
        T cur{};
        for (int i = 1 << std::__lg(n-1); i; i /= 2) {//GCC
            if (x + i <= n-1 && cur + a[x + i] <= k) {
                x += i;
                cur = cur + a[x];
            }
        }
        return x;
    }
};
void solve(){
	cin>>n;cin>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	Fenwick<int>suf(1e6+1),pre(1e6+1);
	int sum=0;
	for(int i=n;i>=1;i--){
		suf.add(a[i],1);
		sum+=suf.sum(a[i]-1);
		//suf.add(a[i],1);
	}
	int ans=0;
	if(sum>=m)ans++;
	for(int i=1,j=1;i<=n;i++){
		suf.add(a[i],-1);
		sum-=suf.sum(a[i]-1);
		sum-=pre.rangeSum(a[i],1000000);
		//suf.add(a[i],-1);
		while(j<=i&&sum<m){
			pre.add(a[j],1);
			sum+=suf.sum(a[j]-1);
			sum+=pre.rangeSum(a[j],1000000);
			//pre.add(a[j],1);
			j++;
		}
		ans+=i-j+1;
		
	}
	cout<<ans<<endl;
	
}

posted @ 2024-03-30 21:28  potential-star  阅读(1)  评论(0编辑  收藏  举报