AtCoder Beginner Contest 473

AtCoder Beginner Contest 473

A - Second Half Sum

直接输出和

void solve(){
    int n,x,s=0;cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x;
        if(i>n/2) s+=x;
    }
    cout<<s<<endl;
}

B - Old Maid

累加出现个数奇数的数之和

void solve(){
    int n,x,s=0;cin>>n;
    vector<int>mp(105);
    for(int i=1;i<=n;i++){
        cin>>x;
        mp[x]^=1;
        if(mp[x]) s+=x;
        else s-=x;
    }   
    cout<<s<<endl;
}

C - Change Schools

对于出现人数到达最大值的班级直接累加,如果次大值刚好是最大值大小-1,对于出现人数到达次大值的班级也进行累加

void solve(){
    int n,k,m1=0,m2=0;
    cin>>n>>k;
    vector<ll>a(n+1);
    vector<ll>mp(k+1);
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        mp[a[i]]++;
    }
    for(int i=1;i<=k;i++){
        if(m1<mp[i]){
            m2=m1;
            m1=mp[i];
        }
        else if(m2<mp[i]&&mp[i]!=m1){
            m2=mp[i];
        }
    }
    int ans=0;
    for(int i=1;i<=k;i++){
        if(mp[i]==m1) ans++;
        else if(mp[i]==m2&&m2+1==m1) ans++;
    }
    cout<<ans<<endl;
}

D - Coefficient Stair

对搜索过程进行限界剪枝,每一位都枚举当且下标的倍数,搜索过程中随着深度不断缩小上界,同时枚举到最后一位时候如果不是n的倍数,直接减去当前分支。

void solve(){
    int n,k;
    cin>>n>>k;
    vector<int>res;
    vector<vector<int>>ans;
    auto dfs=[&](auto &&dfs,int x,int mx) ->void {
        if(mx<0) return;
        if(x==n){
            if(mx%n==0){
                res.push_back(mx/n);
                ans.push_back(res);
                res.pop_back();
            }
            return;
        }
        for(int i=0;i<=mx/x;i++){
            res.push_back(i);
            dfs(dfs,x+1,mx-x*i);
            res.pop_back();
        }
    };
    dfs(dfs,1,k);
    for(auto &v:ans){
        for(int x:v){
            cout<<x<<' ';
        }
        cout<<endl;
    }
}

E - K-Divisible Subarrays

核心观察:

  • 对于某种划分,只有那些区间和 模 k 意义下恒等 0 的区间会对答案产生影响

  • 对于某个位置 \(i\),以当前点为右端点的前缀和为 \(s_i \equiv x \pmod k\) 时,只有那些左端点满足同样为 \(s_{l-1} \equiv x \pmod k\) 的位置能够成功发生转移。又考虑要求最大值,肯定左端点越靠右侧的区间进行转移越优。

因此可以做线性dp,转移方程为 \(dp_i= \max(\max _j^{i-1} {dp_j},dp_{k,s_k \equiv s_i \pmod k } +1)\)

void solve(){
    int n,k;cin>>n>>k;
    vector<ll>dp(n+1);
    vector<ll>a(n+1),pre(n+1);
    for(int i=1;i<=n;i++) cin>>a[i],(pre[i]=pre[i-1]+a[i])%=k;
    map<ll,ll>las;
    las[0]=0;
    ll mx=0;
    for(int i=1;i<=n;i++){
        dp[i]=mx;
        if(las.count(pre[i])) dp[i]=max(dp[i],dp[las[pre[i]]]+1);
        mx=max(mx,dp[i]);
        las[pre[i]]=i;
    }
    cout<<dp[n]<<endl;
}

F - A/AB Insertion

核心观察:

  • 当前子串中 A 的数量严格不小于 B 的数量则子串一定可以通过操作构建出

  • 每次单点修改时,会对连续后缀的 A 数量 与 B 数量产生影响

  • 每次区间询问时,只在乎区间内部 A 数量 与 B 数量相对差值

基于以上三点,固定 A权值为1,B权值为-1,然后维护前缀和的最小值。
这样就转化为了查询区间内部前缀和的最小值与查询区间左侧位置前缀和的值的对比

考虑使用一个支持区间修改,区间最小值查询的数据结构维护,这里使用线段树

string s;
const int N=5e5+5;
const int INF=1e9;
int a[N];
struct Seg{
    struct S{
        int l,r;ll mn,laz;
    };
    vector<S>tre;
    #define lc (p<<1)
    #define rc (p<<1|1)
    Seg(){};
    Seg(int n){
        tre.resize(n<<2);
        build(0,n,1);
    }
    void pushup(int p){
        tre[p].mn=min(tre[lc].mn,tre[rc].mn);
    }
    void pushtag(int p,ll k){
        tre[p].mn+=k;
        tre[p].laz+=k;
    }
    void pushdown(int p){
        if(tre[p].laz==0) return;
        pushtag(lc,tre[p].laz);
        pushtag(rc,tre[p].laz);
        tre[p].laz=0;
    }
    void build(int l,int r,int p){
        int mid=l+r>>1;
        tre[p]=S{l,r,INF,0LL};
        if(l==r){
            tre[p].mn=a[l];
            return;
        }
        build(l,mid,lc);
        build(mid+1,r,rc);
        pushup(p);
    }
    void add(int l,int r,int k,int p){
        if(l>r) return;
        int mid=tre[p].l+tre[p].r>>1;
        if(tre[p].l>=l&&tre[p].r<=r){
            pushtag(p,k);
            return;
        }
        pushdown(p);
        if(l<=mid) add(l,r,k,lc);
        if(r>mid) add(l,r,k,rc);
        pushup(p);
    }
    ll qrymn(int l,int r,int p){
        if(l>r) return INF;
        int mid=tre[p].l+tre[p].r>>1;
        if(l<=tre[p].l&&tre[p].r<=r){
            return tre[p].mn;
        }
        ll mn=INF;
        pushdown(p);
        if(l<=mid) mn=min(mn,qrymn(l,r,lc));
        if(r>mid) mn=min(mn,qrymn(l,r,rc));
        return mn;
    }
};
void solve(){
    int n;cin>>n>>s;
    for(int i=0;i<n;i++) a[i+1]+=(s[i]=='A'),a[i+1]-=(s[i]=='B');//第i位置的mn值
    for(int i=0;i<n;i++) a[i+1]+=a[i];
    Seg S(n);
    int q,op,l,r,x;char c;
    cin>>q;
    while(q--){
        cin>>op;
        if(op==1){
            cin>>x>>c;
            if(c!=s[x-1]){
                s[x-1]=c;
                if(c=='A') S.add(x,n,2,1);
                else S.add(x,n,-2,1);
            }
        }
        else{
            cin>>l>>r;
            if(S.qrymn(l,r,1)>=S.qrymn(l-1,l-1,1)) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
}

G - Wipeout

核心观察:

  • 最优操作下,对于一个位置最多翻两次。因此 \(k>2n\) 时答案为 \(0\)
  • 最终翻k次,等价于恰好存在 \(k-n\) 个位置翻错
  • 对于未知位置数量为 \(i\) 个时,接下来翻对的概率为 \(\frac {1}{i}\),翻错的概率为\(\frac{i-1}{i}\).

最后的概率是把未知量从 \(n\)\(1\) 局面连续递推而来,把每种局面看成一个2次多项式\(\frac {1}{i}+\frac{i-1}{i}x\),则答案就是进行 \(n\) 次多项式乘法后 \(x^{n-k}\) 的系数,因为998244353是ntt友好模数,这里使用ntt算法求解

//Stop learning useless algorithms, go and solve some problems, learn how to use binary search.
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll mod=998244353;
using poly=vector<ll>;
struct NTT {
    static const int MOD = 998244353;
    static const int G = 3;
    /*
    ntt友好模数 原根
    998244353 3
    469762049 3
    167772161 3
    1004535809 3
    754974721 11
    */
    static ll qpow(ll a, ll b) {
        ll res = 1;
        while (b) {
            if (b & 1) res = res * a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return res;
    }
    static void ntt(vector<ll>& a, bool invert) {
        int n = a.size();
        // 位逆序
        for (int i = 1, j = 0; i < n; i++) {
            int bit = n >> 1;
            while (j & bit) {
                j ^= bit;
                bit >>= 1;
            }
            j ^= bit;
            if (i < j) {
                swap(a[i], a[j]);
            }
        }
        // 蝴蝶操作
        for (int len = 2; len <= n; len <<= 1) {
            ll wlen = qpow(G, (MOD - 1) / len);
            if (invert) {
                wlen = qpow(wlen, MOD - 2);
            }
            for (int i = 0; i < n; i += len) {
                ll w = 1;
                for (int j = 0; j < len / 2; j++) {
                    ll u = a[i + j];
                    ll v = a[i + j + len / 2] * w % MOD;
                    ll x = u + v;
                    if (x >= MOD) x -= MOD;
                    ll y = u - v;
                    if (y < 0) y += MOD;
                    a[i + j] = x;
                    a[i + j + len / 2] = y;
                    w = w * wlen % MOD;
                }
            }
        }
        if (invert) {
            ll inv_n = qpow(n, MOD - 2);
            for (ll& x : a) {
                x = x * inv_n % MOD;
            }
        }
    }
    static vector<ll> multiply(vector<ll> a, vector<ll> b) {
        if (a.empty() || b.empty()) {
            return {};
        }
        // 小规模直接暴力卷积,常数更小
        if (min(a.size(), b.size()) <= 32) {
            vector<ll> c(a.size() + b.size() - 1);
            for (int i = 0; i < (int)a.size(); i++) {
                for (int j = 0; j < (int)b.size(); j++) {
                    c[i + j] = (c[i + j] + a[i] * b[j]) % MOD;
                }
            }
            return c;
        }
        int need = (int)a.size() + (int)b.size() - 1;
        int n = 1;
        while (n < need) {
            n <<= 1;
        }
        a.resize(n);
        b.resize(n);
        ntt(a, false);
        ntt(b, false);
        for (int i = 0; i < n; i++) {
            a[i] = a[i] * b[i] % MOD;
        }
        ntt(a, true);
        a.resize(need);
        return a;
    }
};
ll q_pow(ll x,ll y){
    ll s=1;
    while(y){
        if(y&1){
            s=s*x%mod;
        }
        x=x*x%mod;
        y>>=1;
    }
    return s;
}
void solve(){
    int n,k;
    cin>>n>>k;
    queue<poly>Q;
    for(int i=n;i>=1;i--){
        ll invi=q_pow(i,mod-2);
        Q.emplace(poly{invi,(i-1)*invi%mod});//0次项,1次项
    }
    while(Q.size()>=2){
        poly u=Q.front();
        Q.pop();
        poly v=Q.front();
        Q.pop();
        poly w=NTT::multiply(u,v);
        Q.emplace(w);
    }
    poly ans=Q.front();
    k-=n;
    if(k>n) cout<<0<<endl;
    else cout<<ans[k]<<endl;
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    solve();
    return 0;
}
posted @ 2026-08-31 18:48  usedchang  阅读(11)  评论(0)    收藏  举报