(CF2166) Codeforces Round 1064 (Div. 2) 题解

复建 CF 的第二场,打得更糖了。

A. Same Difference *800

给定长度为 \(n\) 的字符串 \(s\),每次操作可以将 \(s_i\) 替换为 \(s_{i+1}\),求最终使得 \(s\) 内每个字符都相同的最小操作次数。

\(2\le n\le 100\)

1s 256MB

由于 \(s_n\) 无法修改,所以最终所有字符必然是 \(s_n\)。从每个 \(=s_n\) 的字符往前修改即可。答案即为 \(\sum\limits_{i=1}^n[s_i\neq s_n]\)

赛时实现很糖,不建议参考 /xk。

#include<bits/stdc++.h>
#define N 
#define ll long long
#define mod 
using namespace std;

void sol()
{
    int a[26]={0},n;
    cin>>n;
    string s;
    cin>>s;
    int maxs=0;
    for(auto i:s) a[i-97]++;
    cout<<n-a[s[n-1]-97]<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}

B. Tab Closing *1000

一个浏览器窗口的宽为 \(a\),同时有 \(n\) 个网页标签在顶部。当窗口中有 \(m\) 个标签时,每个标签的宽度均为 \(\text{len}=\min\left(b,\dfrac am\right)\),并且每个标签的关闭按钮分别在距离窗口左侧 \(\text{len},2\cdot\text{len},\cdots,m\cdot\text{len}\) 的位置。

现在需要点击关闭按钮来关闭所有标签,初始鼠标指针在窗口最左侧,求整个移动过程中鼠标指针最少可以移动的次数。注意操作过程中标签宽度会变化。

\(1\le b\le a\le 10^9\)\(1\le n\le 10^9\)

1.5s 256MB

显然当 \(m\le\left\lfloor\dfrac ab\right\rfloor\) 时,标签宽度固定为 \(b\),只需要将鼠标指针在 \(b\) 的位置一直关闭即可;否则标签会占满整个宽度为 \(a\) 的位置,只需要在 \(a\) 的位置一直关闭即可。注意特判 \(a=b\),答案即为 \(\left[\left\lfloor\dfrac ab\right\rfloor<n\land a\neq b\right]+1\)

#include<bits/stdc++.h>
#define N 
#define ll long long
#define mod 
using namespace std;

void sol()
{
    int a,b,n;
    cin>>a>>b>>n;
    cout<<(a!=b&&a/b<n?2:1)<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}

C. Cyclic Merging *1200

给定一个长度为 \(n\) 的环,环上依次有非负整数 \(a_1,a_2,\cdots,a_n\)。每次操作可以选择一对相邻的数 \(x,y\),并将这两个数替换为 \(\max(x,y)\),并消耗 \(\max(x,y)\) 的代价。求使得序列中最终留下一个数的最小代价。

\(2\le n\le 2\times10^5\)\(0\le a_i\le 10^9\)

2s 256MB

贪心,考虑从小到大删数,对于一个数看跟它左侧或右侧合并哪个更优,链表维护即可。时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define N 200005
#define ll long long
#define mod 
using namespace std;

int n,a[N],pre[N],nxt[N];

void sol()
{
    cin>>n;
    int m=0;
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(!m||a[i]!=a[m]) a[++m]=a[i];
        else ans+=a[i];
    }
    if(a[1]==a[m]&&m>1) ans+=a[m--];
    iota(pre+1,pre+m+1,0);
    pre[1]=m;
    iota(nxt+1,nxt+m+1,2);
    nxt[m]=1;
    set<pair<int,int>>st;
    for(int i=1;i<=m;i++) st.insert({a[i],i});
    for(auto [x,i]:st)
    {
        if(pre[i]==i) break;
        ans+=min(a[pre[i]],a[nxt[i]]);
        // cerr<<x<<','<<i<<' '<<ans<<'\n';
        pre[nxt[i]]=pre[i];
        nxt[pre[i]]=nxt[i];
    }
    cout<<ans<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}

D. Marble Council *2000

给定一个长度为 \(n\) 的序列 \(a\),将其划分成任意多个子序列。对于每个子序列,取出它的众数(若存在多个,取任意一个)加入可重集合 \(S\)(初始 \(S=\varnothing\))。求可以得到的本质不同的 \(S\) 的个数。

\(1\le a_i\le n\le 5000\)

2s 512MB

首先每个 \(S\) 内的元素都代表了一个子序列。

\(c_i\) 为数 \(i\)\(a\) 中的出现次数。先考虑这样一类 \(S\):若有 \(x\in S\),则 \(x\)\(S\) 中的出现次数恰为 \(c_x\)。考察这样的 \(S\) 合法情况及其影响。

  • \(|S|\ge\max\{c_i\}\),那么可以对于每个 \(i\notin S\),将 \(c_i\)\(i\) 分散在 \(c_i\) 个子序列内,这样 \(S\) 内的每个元素始终是众数之一。

    假设有一个 \(T\subset S\) 并且 \(T\) 中数的种类和 \(S\) 相同,此时必然存在一个正整数 \(i\),它在 \(S\) 中的出现次数多于 \(T\)。那么从 \(S\)\(T\) 的过程相当于合并了某两个 \(i\) 代表的子序列。由于合并后的子序列 \(i\) 依旧是出现次数最多的数之一,因此 \(T\) 依旧合法。

  • \(|S|<\max\{c_i\}\),那么存在至少一个 \(c_i\) 没办法将 \(c_i\)\(i\) 分散在恰好 \(c_i\) 个子序列内,此时必然存在一个子序列内 \(i\) 出现了多于一次,于是这时的 \(S\) 自然不合法。

    同样地考虑 \(T\),将两个子序列合并后,要么是这个新的子序列不合法,要么是原来的某个子序列仍旧不合法。

所以我们只要考虑所有满足上述特殊条件的 \(S\) 是否合法即可。对于一个合法的 \(S\),它对答案贡献了 \(\prod\limits_{i\in S}c_i\)

于是问题就变成了:求 \(c\) 所有合法的子序列的乘积和,一个子序列合法当且仅当其内元素之和不小于 \(\max\{c_i\}\)

直接背包。设 \(f_{i,j}\) 表示考虑了正整数 \(1\sim i\),选出的数的元素之和为 \(j\) 的权值之和。容易得到:

\[f_{i,j}=f_{i-1,j}+f_{i-1,j-c_i}\cdot c_i \]

答案即为 \(\sum\limits_{i=\max\{c_i\}}^nf_{n,i}\)。时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define N 5005
#define ll long long
#define mod 998244353
using namespace std;

ll qpow(ll x,ll y)
{
    ll res=1;
    x%=mod;
    while(y)
    {
        if(y&1) res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
int n,a[N],cnt[N];
ll f[N][N];

void sol()
{
    cin>>n;
    fill(cnt+1,cnt+n+1,0);
    int maxc=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        maxc=max(maxc,++cnt[a[i]]);
    }
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            if(j<cnt[i])
            {
                f[i][j]=f[i-1][j];
                continue;
            }
            f[i][j]=(f[i-1][j]+f[i-1][j-cnt[i]]*cnt[i]%mod)%mod;
        }
    }
    ll ans=0;
    for(int i=maxc;i<=n;i++) (ans+=f[n][i])%=mod;
    cout<<ans<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}

E. Binary Wine *2000

给定一个长度为 \(n\) 的非负整数序列 \(a\)\(q\)独立的询问,每次询问给定一个 \(x\)

对于每个询问,每次可以花费 \(1\) 的代价对某一个 \(a_i\) 执行 \(a_i\gets a_i+1\)。求能够使得 \(a\) 满足下列条件的最小代价:

  • 存在一个正整数序列 \(b\),满足对于 \(\forall i\in[1,n]\)\(0\le b_i\le a_i\),且 \(\bigoplus\limits_{i=1}^nb_i=x\)

\(1\le n\le 5\times10^5\)\(1\le q\le 5\times10^4\)\(0\le a_i,x<2^{30}\)

2s 512MB

一个 \(b_i\) 会对 \(x\) 的二进制位上至少贡献一个数位,不然调整一下得到可以将这个数删去的后选择方案。贪心地,\(a_i\) 越大的可选择的数越多,因此只有前 \(30\) 大的 \(a_i\) 是有用的。

将这 \(30\)\(a_i\) 放入一个大根堆中。从高到低考虑 \(x\) 的每个数位。设当前考虑到 \(2^i\) 位,堆顶的数为 \(d\)。讨论二者之间的关系:

  • \(d\ge 2^i\),那么 \(2^i\) 这个数位肯定可以被贡献,然后需要往低位考虑,弹出堆顶并向堆中加入 \(d-2^i\)
  • \(d<2^i\),那么此时 \(2^i\) 这个数位没法被贡献,将 \(d\) 修改到 \(2^i\) 即可,对答案贡献 \(2^i-d\)

时间复杂度 \(O(n\log n+q\log V\log\log V)\)

#include<bits/stdc++.h>
#define N 500005
#define M 31
#define ll long long
#define inf (ll)1e18
#define mod 
using namespace std;

int n,q,a[N],pre[M],nxt[M];
ll f[M][M<<1];

void sol()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+n+1,greater<int>());
    while(q--)
    {
        int x;
        cin>>x;
        priority_queue<int>q;
        for(int i=1;i<=min(30,n);i++) q.push(a[i]);
        while(q.size()<30) q.push(0);
        ll ans=0;
        for(int i=30;~i;i--)
        {
            if(x>>i&1^1) continue;
            int d=q.top();
            q.pop();
            if(d>=(1<<i)) q.push(d-(1<<i));
            else ans+=(1<<i)-d;
        }
        cout<<ans<<'\n';
    }
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}

F. Path Split *2100

给定一个长度为 \(n\) 的正整数序列 \(a\),将 \(a\) 划分成若干个子序列 \(b_1,b_2,\cdots,b_k\),满足对于每个 \(b_i\),满足对于 \(\forall i\in[1,|b_i|)\)\(|b_{i,j}-b_{i,j+1}|=1\)。求 \(\min\{k\}\)

\(1\le n\le 10^6\)\(1\le a_i\le 2n\)

4s 512MB

分别对于奇数和偶数,按值和下标从小到大贪心地找到第一个可以匹配的位置进行匹配。时间复杂度 \(O(n\log n)\)

正确性其实不是很难想,难点在于敢不敢写。这个过程本质上是将 \(a_i\) 按奇偶性划分后进行二分图匹配。\(k\) 越小,匹配越多,因此每个数都需要向后尽可能地匹配,匹配肯定不劣于不匹配。而从小到大贪,本质上是先把限制紧的数匹配掉。

#include<bits/stdc++.h>
#define N 1000005
#define ll long long
#define mod 
using namespace std;

set<int>st[N<<1];
int n,a[N];

void sol()
{
    cin>>n;
    for(int i=1;i<=n*2+1;i++) st[i].clear();
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        st[a[i]].insert(i);
    }
    int ans=n;
    for(int i=1;i<=n*2;i+=2)
    {
        for(auto j:st[i])
        {
            if(!st[i-1].empty()&&*st[i-1].begin()<j)
            {
                auto pos=*--st[i-1].lower_bound(j);
                ans--;
                // cerr<<j<<'-'<<pos<<'\n';
                st[i-1].erase(pos);
                continue;
            }
            if(!st[i+1].empty()&&*st[i+1].begin()<j)
            {
                auto pos=*--st[i+1].lower_bound(j);
                ans--;
                // cerr<<j<<'-'<<pos<<'\n';
                st[i+1].erase(pos);
                continue;
            }
        }
    }
    for(int i=1;i<=n*2+1;i++) st[i].clear();
    for(int i=1;i<=n;i++) st[a[i]].insert(i);
    for(int i=2;i<=n*2;i+=2)
    {
        for(auto j:st[i])
        {
            if(!st[i-1].empty()&&*st[i-1].begin()<j)
            {
                auto pos=*--st[i-1].lower_bound(j);
                ans--;
                // cerr<<j<<'-'<<pos<<'\n';
                st[i-1].erase(pos);
                continue;
            }
            if(!st[i+1].empty()&&*st[i+1].begin()<j)
            {
                auto pos=*--st[i+1].lower_bound(j);
                ans--;
                // cerr<<j<<'-'<<pos<<'\n';
                st[i+1].erase(pos);
                continue;
            }
        }
    }
    cout<<ans<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}
posted @ 2025-11-17 12:21  Jorisy  阅读(333)  评论(5)    收藏  举报