ABC306

A

B

题意:
给定一个01序列,如果是1,答案加上以它下标为次数,2为底数的值
思路:
需要注意long long最大值为2^63 -1,也就是说如果遇到2^63就歇菜了
考虑用ull,或者__int128存储
其中__int128无法用标准的输入和输出,但是可以正常的运算

C

模拟即可,用pii存答案,下标放在右键排序

void solve(){
    int n;cin>>n;
    vector<int>a(3*n+1);
    vector<int>cnt(n+1);
    vector<pii>ans(n+1);
    rep(i,1,n)ans[i].se=i;
    rep(i,1,3*n){
        cin>>a[i];
        cnt[a[i]]++;
        if(cnt[a[i]]==2)ans[a[i]].fi=i;
    }
    sort(ans.begin()+1,ans.end());
    rep(i,1,n){
        cout<<ans[i].se<<' ';
    }cout<<endl;
}

D

题意:
给定n个Xi和Yi,分别代表n个菜肴的两种属性
从左至右,每个菜肴可以吃或者不吃
如果吃下去,答案可以获得该菜肴的Y
有毒的菜肴吃后立即获得中毒状态,此时不可以再次吃下有毒的菜肴,吃下没毒的菜肴可以解毒
没毒的菜肴可以随便吃
问最后的答案最大为多少
思路:
经典线性DP
设计状态,f[i]j
考虑转移
当遇到有毒菜肴时,可以 健康->中毒(吃),中毒->中毒(不吃)也可以健康-> 健康(不吃)
当遇到健康菜肴时, 可以 健康->健康(吃或不吃) 中毒->中毒(不吃) 中毒->健康(吃)

int f[maxn][2];
void solve(){
    int n;cin>>n;
    rep(i,1,n){
        int x,y;cin>>x>>y;
        if(x==1){
            f[i][1]=max(f[i-1][0]+y,f[i][1]);

            f[i][1]=max(f[i-1][1],f[i][1]);
            f[i][0]=max(f[i-1][0],f[i][0]);
        }else{
            f[i][0]=max(f[i-1][0]+y,f[i][0]);
            f[i][0]=max(f[i-1][1]+y,f[i][0]);
            f[i][0]=max(f[i-1][0],f[i][0]);
            f[i][1]=max(f[i-1][1],f[i][1]);
        }
    }
    cout<<max(f[n][0],f[n][1])<<endl;
}

E

题意:
给定一个长度为n的初始为全0的序列
有q次操作,每次给出下标和数值,代表将该下标的数组值换成给定的数值
每次操作后,输出序列中前K大的元素之和
思路:
好像是数据结构题
可以用两个multiset解决
一个用来存前K大的数组值,另一个存剩下来的数组值
用一个变量记录答案
每次操作用multiset的find操作查找该位置的数组值是否在存前K大的multiset里面
若是,答案需要先减
先将原先的数组值移除
考虑插入到哪一个multiset中
显然当比其余元素的最大值还大时,要插入到前k大的multiset,并更新答案
否则插入其余元素的multiset
需要维护两个multiset大小分别是k,n-k
不满足条件时按需要移动

multiset<int>a,b;
//a维护序列中前K大的元素
//b维护序列中剩余的元素(即前n-k小的元素)
void solve(){
    int n,k,q;cin>>n>>k>>q;
    int ans=0;
    vector<int>t(n+1);

    rep(i,1,k){
        a.insert(0);
    }
    rep(i,1,n-k){
        b.insert(0);
    }
    while(q--){
        int x,y;cin>>x>>y;
        if(a.find(t[x])!=a.end()){
            a.erase(a.find(t[x]));
            ans-=t[x];
        }else{
            b.erase(b.find(t[x]));
        }
        t[x]=y;
        if(b.size()&&y>=*b.rbegin()){
            a.insert(y);
            ans+=y;
        }else b.insert(y);

        if(a.size()<k){
            a.insert(*b.rbegin());
            ans+=*b.rbegin();
            b.erase(b.find(*b.rbegin()));
        }
        if(a.size()>k){
            b.insert(*a.begin());
            ans-=*a.begin();
            a.erase(a.begin());
        }

        cout<<ans<<endl;
    }

}

F

题意:
给定一系列没有相同元素的集合,规定f(A,B)为集合A和集合B中元素在同一序列中排序后,集合A元素的下标之和
求f(A,B)之和(比如共有四个集合a,b,c,d,则需要求f(a,b)+f(a,c)+f(a,d)+f(b,c)+f(b,d)+f(c,d))
思路:
显然一个一个枚举集合并且合并再模拟操作时间复杂度不行
考虑每个集合它对于答案的贡献
显然一个f的答案为 一个集合的每一个元素,比它小的元素数量+1 的和
那么一个集合的f的和,分为 1对它的贡献 + 它自身元素对它的贡献 + 其余元素对它的贡献
每一个f:
1对它的贡献就是m,因为它的元素个数有m个
它自身元素对它的贡献为m(m-1)/2,因为下标求和相当于等差数列求和
因此对于f和:它俩的贡献为(n-i)x(m(m+1)/2)
其余元素对它的贡献为每个数比它小的元素数量之和,可以用树状数组(离散化)维护
从后往前遍历统计答案即可

int tr[maxn];
int n,m;
int lowbit(int x){
    return x&-x;
}
void change(int p,int x){
    while(p<=n*m){
        tr[p]+=x;
        p+=lowbit(p);
    }
}
int query(int x){
    int res=0;
    while(x){
        res+=tr[x];
        x-=lowbit(x);
    }
    return res;
}

void solve(){
    cin>>n>>m;
    vector<vector<int>>a(n+1,vector<int>(m+1,0));
    map<int,int>mp;
    vector<int>k;
    rep(i,1,n){
        rep(j,1,m){
            cin>>a[i][j];
            k.pb(a[i][j]);
        }
        sort(a[i].begin()+1,a[i].end());
    }
    sort(k.begin(),k.end());
    int len=unique(k.begin(),k.end())-k.begin();
    int ans=0;
    for(int i=0;i<len;i++)mp[k[i]]=i+1;
    

    for(int i=n;i>=1;i--){
        int res=(n-i)*m*(m+1)/2;
        for(int j=m;j>=1;j--){
            change(mp[a[i][j]],1);
            ans+=query(mp[a[i][j]]-1);
        }
        ans+=res;
    }
    cout<<ans<<endl;
}

posted @ 2025-06-05 22:27  Marinaco  阅读(19)  评论(0)    收藏  举报
//雪花飘落效果