Codeforces Round 979 (Div. 2) D/E

这场跨度好大

D. QED's Favorite Permutation

原题链接:https://mirror.codeforces.com/contest/2030/problem/D

思路:

\(L\) 只能与当前的前一个进行交换,\(R\) 只能与当前的后一个进行交换,容易发现当前一个字符与后一个字符分别为 \(L\)\(R\) 时,以此为分割,前半部分与后半部分无法进行交换,所以若无法进行排序,则在此分割的前半段的最大值一定大于后半段的最小值。因为我们每次要对特定位置的字符进行修改,然后再去判断是否可以排序。一个可行的方案是:我们预处理前缀最大和后缀最小分别为 \(pre[]\)\(suf[]\),然后找到所有 \(LR\),查看在此处是否非法,我们用\(cnt\)去记录这样的非法状态数,若非法则 \(cnt++\),然后每次查询我们只去查询和修改对应位置相关的位置,判断修改后 \(cnt\) 是否会发生变化,若修改后 \(cnt\) 仍然大于 \(0\),那么显然整个序列非法,否则合法输出 \(YES\)

时间复杂度 O(n+q)

预处理和 \(cnt\) 计数都是 \(O(n)\) 的,每次查询都只会检查修改点前后两个位置为O(1),总体为O(q)。

代码

#include<bits/stdc++.h>
using namespace std;    

typedef long long ll;
typedef pair<int,int> PII;

const int N=2e5+10;

int n,q;

void solve(){
    cin>>n>>q;
   
    vector<int> a(n+1);
    for(int i=1;i<=n;i++)cin>>a[i];

    vector<int> pre(n+1,0),suf(n+2,1e9);
    for(int i=1;i<=n;i++){
        pre[i]=max(pre[i-1],a[i]);
    }
    for(int i=n;i;i--){
        suf[i]=min(suf[i+1],a[i]);
    }

    string s;
    cin>>s; s=" "+s;

    int cnt=0;
    for(int i=1;i<n;i++){
        if(s[i]=='L'&&s[i+1]=='R'&&pre[i]>suf[i+1]){
            cnt++;
        }
    }

    while(q--){
        int x;
        cin>>x;

        if(s[x-1]=='L'&&s[x]=='R'&&pre[x-1]>suf[x])cnt--;
        if(s[x]=='L'&&s[x+1]=='R'&&pre[x]>suf[x+1])cnt--;
        
        s[x]=s[x]=='L'?'R':'L';

        if(s[x-1]=='L'&&s[x]=='R'&&pre[x-1]>suf[x])cnt++;
        if(s[x]=='L'&&s[x+1]=='R'&&pre[x]>suf[x+1])cnt++;

        if(cnt) cout<<"NO\n";
        else cout<<"YES\n";
    }
}

int main() {
    cin.tie(0)->sync_with_stdio(false);
    cout.tie(0);

    int t=1;
    cin>>t;

    while(t--)solve();
}

E. MEXimize the Score

原题链接:https://mirror.codeforces.com/contest/2030/problem/E

看了这篇题解才会了:https://www.cnblogs.com/YipChipqwq/p/18498462/CF2030E

思路:

对于无法直接进行求解的题,一般要么正难则反,要么考虑每部分对答案的贡献。

在这里显然无法直接求所有子序列然后求解,也没法反方向进行考虑,只能计算每部分对答案的贡献了。

对于一个完美划分的序列的价值 \(sum\) 等价于 \(min(cnt0)\) + \(min(cnt0,cnt1)\) + ... + \(min(cnt0,cnt1,...,cntt)\),那么一个贪心的想法是把相同的数字分到不同的集合。

然后我们就可以构造一个包含数字 \(t\) 的集合,这个集合的价值可以通过构造尽可能长的 \(0、1、...、t\) 的排列得到,排列存在当且仅当 \(min(cnt0, cnt1, ..., cntt)>0\)。 我们发现排列中每个数字对答案的贡献恰好为 \(1\)

那么对于一个特定的数字 \(t\)\(f(t)=\sum_{i=k}^{cnt_t} C(cnt_t,i)\),可以视为先从 \(cnt_t\) 中选出 \(k\) 个插入到 \(k\)\(0 ~ t-1\) 的序列中,这部分对答案的贡献为 \(1\),然后再把剩余的 \(i-k\) 个加入到集合中,这部分起到增加集合状态数的作用,然后剩余的大于 \(t\) 且未选择的个数 \(r\),也可以产生 \(2^r\) 个集合,这些集合插入不会对 \(t\) 的贡献产生影响,也是起到增加集合种类数的作用,所以要在乘上这个数。

具体做法就是:每次选择 \(k\) 作为这种排列的个数,然后计算每种 \(t\) 对答案的贡献,并且有 \(min(cnt0, cnt1, ..., cntt) ≥ k\),对 \(0\) 来说拿出 \(k\) 个作为合法集合,共有 \(C(cnt_0,k)\) 种方案,在考虑将多余的 \(0\) 加入,这部分的贡献就是 \(f(0)\),然后后缀还有 \(suf[0]\) 种可能(未选个数的2的幂次),相乘后直接加入答案中。

\(t\) 时,前 \(t-1\) 已经处理完毕,共有 \(\prod_{i=0}^{t-1} f(i)\) 种集合,对于其后的元素是否加入还有 \(suf[i]\) 种可能,然后就可以将所有答案求出。

代码

#include<bits/stdc++.h>
using namespace std;    

typedef long long ll;
typedef pair<int,int> PII;

const int N=2e5+10,mod=998244353;

int n;

ll pow2[N],fact[N],infact[N];

ll C(int a,int b){ return fact[a]*infact[b]%mod*infact[a-b]%mod; }

void solve(){
    cin>>n;

    vector<int> cnt(n,0);

    int a;
    for(int i=0;i<n;i++){
        cin>>a;
        cnt[a]++;
    }

    vector<ll> suf(n);
    
    suf[n-1]=1;
    for(int i=n-1;i;i--){
        suf[i-1]=suf[i]*pow2[cnt[i]]%mod;
    }

    ll ans=0,p=0;
    vector<int> f(n),g(n);
    g=cnt;

    for(int k=cnt[0];k;k--){
        while(p<n&&cnt[p]>=k)p++;
        ll res=1;
        for(int i=0;i<p;i++){
            while(g[i]>=k)(f[i]+=C(cnt[i],g[i]))%=mod,g[i]--;  // 求选至少k时的组合数
            res=res*f[i]%mod;  // 乘法原则
            ans=(ans+res*suf[i])%mod;  //未选的部分加入影响集合的种类 但对i在排列中的贡献并不产生影响
        }
    }

    cout<<ans<<endl;
}

// qmi
ll qmi(ll a,ll k){
    ll res=1;
    while(k){
        if(k&1)res=res*a%mod;
        a=a*a%mod;
        k>>=1;
    }
    return res;
}

// 初始化预处理阶乘和2的幂次
void init(){  
    pow2[0]=fact[0]=infact[0]=1;
    for(int i=1;i<N;i++){
        pow2[i]=2ll*pow2[i-1]%mod;
        fact[i]=fact[i-1]*i%mod;
    }
    
    infact[N-1]=qmi(fact[N-1],mod-2);
    for(int i=N-1;i;i--){
        infact[i-1]=infact[i]*i%mod;
    }
}

int main() {
    cin.tie(0)->sync_with_stdio(false);
    cout.tie(0);

    init();

    int t=1;
    cin>>t;

    while(t--)solve();
}

posted @ 2025-02-25 14:11  宋佳奇  阅读(13)  评论(0)    收藏  举报