AtCoder Beginner Contest 324题解(F,G)

文章讲述了如何在给定的无向图中找到从节点1到节点N的路径,具有最大“美丽值”的解决方案,以及处理两种序列操作(移除元素和移除部分序列)的高效算法。使用了动态规划和二分搜索,并提到了可持久化线段树的应用。
摘要由CSDN通过智能技术生成

ABC324题解(F,G)

F - Beautiful Path

题意:给定一张无向图,其中每条边 e i e_i ei有两个边权 a i a_i ai b i b_i bi,任意一条路径有一个“美丽值”定义为 X = ∑ a i ∑ b i X=\frac{\sum{a_i}}{\sum{b_i}} X=biai,求从节点1到节点N的路径最大的“美丽值”

解答:将式子做变换,一条路径的美丽值满足 ∑ a i − b i X = 0 \sum{a_i-b_iX}=0 aibiX=0,X是节点1-N所有路径最大的美丽值当且仅当对任意从1-N的路径, m a x ( ∑ a i − b i X ) = 0 max(\sum{a_i-b_iX})=0 max(aibiX)=0,所以对X进行二分搜索,对每一个X,用DP来计算1-N节点 m a x ( ∑ a i − b i X ) max(\sum{a_i-b_iX}) max(aibiX)

代码:

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr double EPS = 1e-11;
constexpr double inf = 1e10;
struct Edge{
    int u,v;
    int b,c;
    int next=-1;
};
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,m;
    cin>>n>>m;
    vector<int> head(n,-1);
    vector<Edge> e(m);
    for(int i=0;i<m;i++){
        int u,v,b,c;
        cin>>u>>v>>b>>c;
        u--;v--;
        e[i]={u,v,b,c,head[v]};
        head[v]=i;
    }
    double l=0,r=1e10;
    function<bool(double)> check=[&](double x){
        vector<double> dp(n,-inf);
        dp[0]=0;
        for(int i=1;i<n;i++){
            for(int j=head[i];j!=-1;j=e[j].next){
                int u=e[j].u;
                int b=e[j].b;
                int c=e[j].c;
                dp[i]=max(dp[i],dp[u]+(double)b-x*c);
            }
        }
        if(dp[n-1]<0){
            return false;
        }
        return true;
    };
    while(l<r){
        if(r-l<EPS) break;
        double mid=l+(r-l)/2;
        if(check(mid)){
            l=mid;
        }
        else{
            r=mid;
        }
    }
    cout<<fixed<<setprecision(10)<<r<<'\n';
    return 0;
}

G - Generate Arrays

题意:给定一个1-N的排列作为序列0,然后不断从此前某个序列中要么移除第k个元素之后的所有元素组成新序列,要么移除大于某个数的所有元素构成新序列。问每次操作后得到的新序列的长度。

解答:

法一:直接模拟

用set来表示序列,移除元素增加元素就用erase和insert,重点是时间复杂度的分析。

原操作相当于对序列不断的分拆,我们倒过来考虑对序列不断的合并,而且总让序列短的合并进序列长的,被合并的元素由于每次合并后所在序列长度增加一倍,最多被合并 log ⁡ N \log{N} logN次,而每次合并复杂度为 log ⁡ N \log{N} logN,又有N个元素,所以复杂度为 O ( N log ⁡ 2 N ) O(N\log^2{N}) O(Nlog2N)。为了让移除大于某个数操作的erase操作最少,可以用两个set以对顶堆的形式维护中位数,若给定数大于中位数,则把原序列不断弹出最大的,若小于中位数,则交换两序列位置,不断弹出最小的元素给原序列的位置。

代码:

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
struct Sequence{
    using value = pair<int,int>;
    set<value> seq,large,small;
    void balance(){
        while(small.size()>large.size()){
            large.emplace(*prev(small.end()));
            small.erase(prev(small.end()));
        }
        while(small.size()+1<large.size()){
            small.emplace(*large.begin());
            large.erase(large.begin());
        }
        return;
    }
    void insert(const value& v){
        insert(v.first,v.second);
        return;
    }
    void insert(int i,int v){
        seq.emplace(i,v);
        if(large.empty()||v>=large.begin()->first){
            large.emplace(v,i);
        }
        else{
            small.emplace(v,i);
        }
        balance();
        return;
    }
    value pop_front(){
        value v=*seq.begin();
        seq.erase(seq.begin());
        large.erase(make_pair(v.second,v.first));
        small.erase(make_pair(v.second,v.first));
        balance();
        return v;
    }
    value pop_back(){
        value v=*prev(seq.end());
        seq.erase(prev(seq.end()));
        large.erase(make_pair(v.second,v.first));
        small.erase(make_pair(v.second,v.first));
        balance();
        return v;
    }
    value pop_max(){
        value v=*prev(large.end());
        large.erase(prev(large.end()));
        seq.erase(make_pair(v.second,v.first));
        balance();
        return make_pair(v.second,v.first);
    }
    value pop_min(){
        value v=*(small.empty()?large:small).begin();
        if(small.empty()){
            large.erase(large.begin());
        }
        else{
            small.erase(small.begin());
        }
        seq.erase(make_pair(v.second,v.first));
        balance();
        return make_pair(v.second,v.first);
    }
    int mid(){
        return large.begin()->first;
    }
    int max(){
        return prev(large.end())->first;
    }
    int min(){
        return (small.empty()?large:small).begin()->first;
    }
    int size(){
        return seq.size();
    }
    int empty(){
        return seq.empty();
    }
};
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    vector<Sequence> seqs(1);
    for(int i=0;i<n;i++){
        int t;
        cin>>t;
        seqs[0].insert(i,t);
    }
    int q;
    cin>>q;
    seqs.resize(q+1);
    for(int i=1;i<=q;i++){
        int t,s,x;
        cin>>t>>s>>x;
        int m=seqs[s].size();
        if(t==1){
            if(x*2<m){
                swap(seqs[s],seqs[i]);
                for(int j=0;j<x;j++){
                    seqs[s].insert(seqs[i].pop_front());
                }
            }
            else if(x<m){
                for(int j=0;j<m-x;j++){
                    seqs[i].insert(seqs[s].pop_back());
                }
            }
        }
        else{
            if(seqs[s].empty()){
                cout<<"0\n";
                continue;
            }
            if(x>seqs[s].mid()){
                while(!seqs[s].empty()&&seqs[s].max()>x){
                    seqs[i].insert(seqs[s].pop_max());
                }
            }
            else{
                swap(seqs[i],seqs[s]);
                while(!seqs[i].empty()&&seqs[i].min()<=x){
                    seqs[s].insert(seqs[i].pop_min());
                }
            }
        }
        cout<<seqs[i].size()<<'\n';
    }
    return 0;
}

法二:可持久化线段树(主席树)

考虑把序列每一个元素看成 ( i , a i ) (i,a_i) (i,ai),即在二维平面上的一个点,而序列就是在这个平面上的一块区域,每次操作就是在分拆区域。移除大于a的元素操作很简单,就是把区域以y=a为界分为两块就行。移除一个序列内第k个数之后的所有数有一点麻烦。这里我们选择“侧着”建立主席树,即值域不是序列内元素而是元素的下标。我们以元素从小到大的顺序依次插入它们的下标,这样在查询 d ≤ a i ≤ u d\leq a_i\leq u daiu组成的序列的第k个元素的下标其实就是求主席树 ( d , u ) (d,u) (d,u)区间第k小,复杂度为 log ⁡ N \log{N} logN。每次查询区域内元素个数复杂度也是 log ⁡ N \log{N} logN,最终复杂度为 O ( N log ⁡ N ) O(N\log{N}) O(NlogN)

代码:

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
struct PersistentSegmentTree{
    struct Node{
        int l=-1,r=-1,val=0;
    };
    int n,tot;
    vector<Node> tree;
    vector<int> root;
    PersistentSegmentTree(int _n): n(_n),tot(0) {
        tree.resize(n<<5);
        int r=build(0,n-1);
        root.push_back(r);
    }
    int build(int l,int r){
        int root=++tot;
        if(l==r) return root;
        int mid=(l+r)/2;
        tree[root].l=build(l,mid);
        tree[root].r=build(mid+1,r);
        return root;
    }
    int update(int k,int p,int l,int r){
        int rt=++tot;
        tree[rt]=tree[p];
        tree[rt].val++;
        if(l==r){
            return rt;
        }
        int mid=(l+r)/2;
        if(k<=mid){
            tree[rt].l=update(k,tree[rt].l,l,mid);
        }
        else{
            tree[rt].r=update(k,tree[rt].r,mid+1,r);
        }
        return rt;
    }
    void update(int k){
        int pre=root.back();
        int rt=update(k,pre,0,n-1);
        root.push_back(rt);
    }
    int query(int ceil,int pl,int pr,int l,int r){
        if(ceil>=r){
            return tree[pr].val-tree[pl].val;
        }
        else if(ceil<l){
            return 0;
        }
        int mid=(l+r)/2;
        if(mid>=ceil){
            return query(ceil,tree[pl].l,tree[pr].l,l,mid); 
        }
        else{
            return tree[tree[pr].l].val-tree[tree[pl].l].val+query(ceil,tree[pl].r,tree[pr].r,mid+1,r);
        }
    }
    // 区域内查询元素个数 O(logn)
    int query(int floor,int ceil,int l,int r){
        if(floor>ceil||l>r){
            return 0;
        }
        return query(ceil,root[l],root[r+1],0,n-1)-query(floor-1,root[l],root[r+1],0,n-1);
    }
    int kth(int k,int pl,int pr,int l,int r){
        int mid=(l+r)/2;
        if(l==r){
            return l;
        }
        int lval=tree[tree[pr].l].val-tree[tree[pl].l].val;
        if(k>lval){
            return kth(k-lval,tree[pl].r,tree[pr].r,mid+1,r);
        }
        else{
            return kth(k,tree[pl].l,tree[pr].l,l,mid);
        }
    }
    // 区间第k小 O(logn)
    int kth(int k,int l,int r){
        return kth(k,root[l],root[r+1],0,n-1);
    }
};
struct Area{
    int l=0,r=-1,d=0,u=-1;
};
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,q;
    cin>>n;
    PersistentSegmentTree tree(n);
    vector<int> arr(n);
    for(int i=0;i<n;i++){
        int t;
        cin>>t;
        t--;
        arr[t]=i;
    }
    for(int i=0;i<n;i++){
        tree.update(arr[i]);
    }
    cin>>q;
    vector<Area> seq(1+q);
    seq[0]={0,n-1,0,n-1};
    for(int i=1;i<=q;i++){
        int t,s,x;
        cin>>t>>s>>x;
        seq[i]=seq[s];
        if(t==1){
            int cnt=tree.query(seq[i].l,seq[i].r,seq[i].d,seq[i].u);
            if(!x){
                seq[s].l=seq[s].r+1;
            }
            else if(x>cnt){
                seq[i].l=seq[i].r+1;
            }
            else{
                int m=tree.query(0,seq[i].l-1,seq[i].d,seq[i].u);
                seq[s].r=tree.kth(x+m,seq[s].d,seq[s].u);
                seq[i].l=seq[s].r+1;
            }
        }
        else{
            x--;
            if(seq[s].d>x){
                seq[s].d=seq[s].u+1;
            }
            else if(seq[s].u>=x){
                seq[s].u=x;
                seq[i].d=x+1;
            }
            else{
                seq[i].d=seq[i].u+1;
            }
        }
        cout<<tree.query(seq[i].l,seq[i].r,seq[i].d,seq[i].u)<<'\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值