CodeForces - 1129D Isolation

题意:

给定一个长度为 n n n 的数组 a a a,求有多少划分方式使得每段区间中出现次数为 1 1 1 的数字个数不大于 k k k,答案模 998244353 998244353 998244353 ( n , k , a i ≤ 1 0 5 ) (n, k, a_i \leq 10^5) (n,k,ai105)

链接:

https://codeforces.com/contest/1129/problem/D

解题思路:

很容易得到 d p dp dp 状态转移方程,记 f [ i ] f[i] f[i] 为前缀 i i i 的划分答案,则
f [ 0 ] = 1 , f [ i ] = ∑ j = 1 i f [ j − 1 ] ∗ [ c n t ( j , i ) ≤ k ] f[0] = 1, f[i] = \sum\limits_{j = 1}^{i} f[j - 1] * [cnt(j, i) \leq k] f[0]=1,f[i]=j=1if[j1][cnt(j,i)k]
其中, c n t ( j , i ) cnt(j, i) cnt(j,i) 表示区间 [ j , i ] [j, i] [j,i] 频率为 1 1 1 的数字个数,可以动态维护 i i i 作为右端点时每个 j j j i i i c n t cnt cnt 值。

i − 1 → i i - 1 \rightarrow i i1i,记 l a s t [ a [ i ] ] 、 l l a s t [ a [ i ] ] last[a[i]]、llast[a[i]] last[a[i]]llast[a[i]] a [ i ] a[i] a[i] 上次、上上次出现的位置,则相应更新操作为区间 [   l a s t [ a [ i ] ] + 1 , i   ] [~last[a[i]] + 1, i~] [ last[a[i]]+1,i ] 1 1 1,区间 [   l l a s t [ a [ i ] ] + 1 , l a s t [ a [ i ] ]   ] [~llast[a[i]] + 1, last[a[i]]~] [ llast[a[i]]+1,last[a[i]] ] 1 1 1,那么 c n t ( j , i ) cnt(j, i) cnt(j,i) 的值对应一个单点值。

然后分块维护 c n t ( j , i ) ≤ k cnt(j, i) \leq k cnt(j,i)k 对应的 f [ j − 1 ] f[j - 1] f[j1] 的和即可。

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e5 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
 
const int maxm = 333;
int a[maxn], last[maxn], llast[maxn], f[maxn];
int id[maxn], li[maxm], ri[maxm], g[maxn], add[maxm], sum[maxm][maxn], ans[maxm];
int n, k;

void build(){

    int len = sqrt(n);
    for(int i = 1; i <= n; ++i) id[i] = (i - 1) / len + 1;
    for(int i = 1; i <= id[n]; ++i) li[i] = (i - 1) * len + 1, ri[i] = i * len;
    ri[id[n]] = n;
}

inline void MOD(int &x){

    x %= mod, x += x < 0 ? mod : 0;
}

void pushDown(int p){

    for(int i = li[p]; i <= ri[p]; ++i) sum[p][g[i]] = 0;
    ans[p] = 0;
    for(int i = li[p]; i <= ri[p]; ++i){

        g[i] += add[p];
        sum[p][g[i]] += f[i - 1], MOD(sum[p][g[i]]);
        ans[p] += g[i] <= k ? f[i - 1] : 0, MOD(ans[p]);
    }
    add[p] = 0;
}

void update2(int l, int r, int val){

    int p = id[l];
    pushDown(p);
    for(int i = l; i <= r; ++i){

        sum[p][g[i]] -= f[i - 1], MOD(sum[p][g[i]]);
        ans[p] -= g[i] <= k ? f[i - 1] : 0, MOD(ans[p]);
        g[i] += val;
        sum[p][g[i]] += f[i - 1], MOD(sum[p][g[i]]);
        ans[p] += g[i] <= k ? f[i - 1] : 0, MOD(ans[p]);
    }
}

void update(int l, int r, int val){

    int p1 = id[l], p2 = id[r];
    if(p1 == p2){

        update2(l, r, val);
        return;
    }
    for(int i = p1 + 1; i < p2; ++i){

        if(val == 1 && k - add[i] >= 0) ans[i] -= sum[i][k - add[i]], MOD(ans[i]);
        else if(val == -1 && k + 1 - add[i] >= 0) ans[i] += sum[i][k + 1 - add[i]], MOD(ans[i]);
        add[i] += val;
    }
    update2(l, ri[p1], val);
    update2(li[p2], r, val);
}

int query(){

    int ret = 0;
    for(int i = 1; i <= id[n]; ++i) ret += ans[i], MOD(ret);
    return ret;
}

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    build();
    f[0] = 1;
    for(int i = 1; i <= n; ++i){

        update(last[a[i]] + 1, i, 1);
        if(last[a[i]]) update(llast[a[i]] + 1, last[a[i]], -1);
        llast[a[i]] = last[a[i]], last[a[i]] = i;
        f[i] = query();
    }
    cout << f[n] << endl;
    return 0;
}
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值