论如何 AK WFYZ【78edu周测】-7

· · 题解

contest url

本场比赛题目来源是 Codeforces Educational round#3 的 B,C,D,E,F 题

难度分别为 1100,1500,2000,2100,2500,被 lxn 出到了 OI 赛制比赛,直接复制 CF 样例。

很遗憾,本场比赛每道题都写了正解,然而因为愚蠢的错误挂了一堆分。

A

枚举每一种书,计算这种书能与其他种类的书组成多少种,最后求和。

然后注意到每一对答案算了两边,答案要除以 2

B

\bar aa 的平均值。
那么最后最小极差显然是 \lceil \bar a \rceil - \lfloor \bar a \rfloor

显然,优先用比 \lceil \bar a \rceil 大的数去补充比 \lfloor \bar a \rfloor 小的数,然后再用恰好是 \lceil \bar a \rceil 的位置去补。如果比 \lceil \bar a \rceil 大的数补完后还有剩余,那么剩下的多余总和也要进行操作。

感觉上面说的不清楚,代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
const int maxn=2e5+5;
int n;
int m[maxn];
void solve(){
    scanf("%d",&n);
    int sum=0;
    FOR(i,1,n){
        scanf("%d",&m[i]);
        sum+=m[i];
    }
    //printf("sum = %d n = %d\n",sum,n);
    int adj=sum/n;
    int mx=adj+(sum%n!=0);
    int mn=adj;
    ll tot=0;
    // 比 adj 小的,必须到达 adj
    // 由 比 adj 大的提供援助
    // 但是可能比 adj 大的存在剩余
    // 此时需要计算剩余多少,然后
    ll rest=0;
  //  printf("mx = %d\n",mx);
    FOR(i,1,n){
        if(m[i]>mx){
            rest+=m[i]-mx;
        }
    }
    FOR(i,1,n){
        if(m[i]<mn){
            tot+=mn-m[i];
            rest-=(mn-m[i]);
        }
    }
    printf("%lld\n",tot+max(0ll,rest));
}
int main(){
    solve();
    return 0;
}

C

显然随着天数增加,花费始终是不增的。

于是就有了单调性,可以二分天数。

对于一个固定的天数,我们可以知道美元/欧元付款的物品价格哪天最便宜。

然后在最便宜的那天付款就 ok 了。

// LUOGU_RID: 154894943
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
const int maxn=2e5+5;
int n,m,k,s;
ll bro3_to_us[maxn];
ll bro3_to_uk[maxn];
ll preminus[maxn],preminuk[maxn];
ll preidus[maxn],preiduk[maxn];// 前 i 个最小值位置 
struct Seller{
    int id;
    int tag;
    ll money;
    ll finalmoney;
}seller[maxn];
vector<pair<ll,ll> > chk(int day){
    FOR(i,1,m){
        if(seller[i].tag==1){
            seller[i].finalmoney=seller[i].money*preminus[day];
        }else{
            seller[i].finalmoney=seller[i].money*preminuk[day];
        }   
    }
    sort(seller+1,seller+m+1,[&](Seller& a,Seller& b){
        return a.finalmoney<b.finalmoney;
    });
    ll sum=0;
    FOR(i,1,k){
        sum+=seller[i].finalmoney;
    }
    vector<pair<ll,ll> > ans;
    if(sum>s){
        return ans;
    }else{
        FOR(i,1,k){
            int id;
            if(seller[i].tag==1)id=preidus[day];
            else id=preiduk[day];
            ans.emplace_back(make_pair(seller[i].id,id));
        }
        return ans;
    }
}
void solve(){
    scanf("%d%d%d%d",&n,&m,&k,&s);
    preminus[0]=preminuk[0]=1e18;
    FOR(i,1,n){
        scanf("%lld",&bro3_to_us[i]);
        preminus[i]=min(preminus[i-1],bro3_to_us[i]);
        if(bro3_to_us[i]<preminus[i-1]){
            preidus[i]=i;
        }else{
            preidus[i]=preidus[i-1];
        }
    //  printf("preminus[%d] = %lld\n",i,preminus[i]);
    //  printf("preidus[%d] = %lld\n",i,preidus[i]);
    }
    FOR(i,1,n){
        scanf("%lld",&bro3_to_uk[i]);
        preminuk[i]=min(preminuk[i-1],bro3_to_uk[i]);
        if(bro3_to_uk[i]<preminuk[i-1]){
            preiduk[i]=i;
        }else{
            preiduk[i]=preiduk[i-1];
        }
    }
    FOR(i,1,m){
        seller[i].id=i;
        scanf("%d%lld",&seller[i].tag,&seller[i].money);
    }
    int l=1,r=n;
    int day=-1;
    vector<pair<ll,ll> >  ans;
    while(l<=r){
        int mid=(l+r)/2;
        auto tmp=chk(mid);
        if(!tmp.empty()){
            ans=tmp;
            day=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    printf("%d\n",day);
    for(int i=0;i<ans.size();i++){
        printf("%lld %lld\n",ans[i].first,ans[i].second);
    }
}
int main(){
    solve();
    return 0;
}

D

考虑先求出 MST。
然后对于每一次固定一条边,分两种情况:

// fa[i][u] 是 u 的 2^i 次向上 // val[i][u] 是 u 开始,共 2^i 条边,到达 fa[i][u] 的最大值。 void dfs(int u,int _fa,ll lstweight){ val[0][u]=lstweight; fa[0][u]=_fa; dep[u]=dep[_fa]+1; FOR(i,1,20){ fa[i][u]=fa[i-1][fa[i-1][u]]; } FOR(i,1,20){ val[i][u]=max(val[i-1][u],val[i-1][fa[i-1][u]]); } for(int i=0;i<g[u].size();i++){ int v=g[u][i].first,w=g[u][i].second; if(v!=_fa){ dfs(v,u,w); } } } int query(int a,int b){ if(dep[a]<dep[b])swap(a,b); int ans=0; REP(i,20,0){ if(dep[fa[i][a]]>=dep[b]){ ckmx(ans,val[i][a]); a=fa[i][a]; } } if(a==b)return ans; REP(i,20,0){ if(fa[i][a]!=fa[i][b]){ ckmx(ans,val[i][a]); ckmx(ans,val[i][b]); a=fa[i][a]; b=fa[i][b]; } } ckmx(ans,max(val[0][a],val[0][b])); return ans; } bool ismst[maxn]; void solve(){ scanf("%d%d",&n,&m); FOR(i,1,m){ int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w,i); } ms.init(); sort(edge+1,edge+m+1,[&](EDGE& a,EDGE& b){ return a.w<b.w; }); int rest=n-1; ll mstval=0; FOR(i,1,m){ if(ms.get(edge[i].u)!=ms.get(edge[i].to)){ rest--; ms.ms(edge[i].u,edge[i].to); ismst[edge[i].id]=1; mstval+=edge[i].w; int u=edge[i].u,to=edge[i].to,w=edge[i].w; g[u].emplace_back(make_pair(to,w)); g[to].emplace_back(make_pair(u,w)); if(rest==0)break; } } dfs(1,0,1e9); sort(edge+1,edge+m+1,[&](EDGE& a,EDGE& b){ return a.id<b.id; }); FOR(i,1,m){ if(ismst[i]){ printf("%lld\n",mstval); }else{ ll mx=query(edge[i].u,edge[i].to); printf("%lld\n",mstval+edge[i].w-mx); } } } int main(){ solve(); return 0; }

## E  
考虑依次模拟。   
1. 加入一个新的苍蝇,将其记录下来,我们先看看哪只青蛙会吃掉它。  
2. 如果没有青蛙能吃掉它,跳到步骤 1
3. 对于能吃掉它的青蛙,我们首先需要知道它的编号。  
4. 随后我们计算出这只青蛙本次**单次**能吃掉的苍蝇总数,以及以此获得的奖励  
5. 将这只青蛙本次单次吃掉的苍蝇算进这只青蛙最后的答案,并更新这只青蛙的捕食范围  
6. 如果这只青蛙能再吃一些新的苍蝇,跳到步骤 4。否则退出,跳到步骤 1。  

注意到本过程中,我们关心每个位置对应的青蛙,另外,青蛙会增加捕食范围。注意到每个位置会尽可能对应位置最小的青蛙,同时所有青蛙位置不同,所以我们可以用线段树记录每个位置对应的最小的青蛙**的位置**,并用 map 将每个青蛙的位置映射到对应的青蛙编号。这时候,青蛙捕食范围增加对应了区间取 min,也就是说,我们需要维护一个线段树,支持区间取 min,单点查询。    

另外,还需要计算单次捕食吃了多少苍蝇,以及对应的奖励,同时捕食结束后被吃掉的苍蝇会消失,可以得出我们需要维护另一个线段树,支持区间总苍蝇数,区间总奖励,区间删除所有苍蝇,本质对应区间修改(设置为 0),区间求和。  

```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
/*
离散化,计算每个位置被覆盖的最小编号,用 sgt1 存储 
依次枚举每只蚊子,并将其加入 sgt2 
每加入一只蚊子,就判断是否被覆盖  
如果被覆盖,那么看是哪个位置开头的青蛙将其覆盖,然后根据 sgt2 进行修改 
 
sgt1 维护单点最小值,支持区间 min 
sgt2 维护区间和,支持区间重置为 0 
 
另外维护每个位置的大小和长度 
 
 具体的,每次枚举到一个蚊子,查看覆盖它的青蛙编号  
从那个青蛙开始,查询对应影响区间在 sgt2 的区间和,记为 sum,然后这只青蛙吞食长度增加 sum, sgt1 对应修改,并再次查询区间和,以此类推 
*/
const int maxn=2e5+5;
int n,m;
struct Disc{
    ll lsh[maxn*3];
    int top;
    void add(ll x){
        lsh[++top]=x;
    }
    void init(){
        sort(lsh+1,lsh+top+1);
        top=unique(lsh+1,lsh+top+1)-lsh-1;
    }
    int pre(int x){
        // 小于等于 x 的最后一个位置 
        return upper_bound(lsh+1,lsh+top+1,x)-lsh-1; 
    }
    int suf(int x){
        return lower_bound(lsh+1,lsh+top+1,x)-lsh;// 大于等于 x 的第一个位置 
    }
}disc;
struct QWQ{
    ll pos,len;
}qwq[maxn];
struct WZ{
    ll pos,reward;
}wz[maxn];
struct SGT1{
    int L[maxn*3*4],R[maxn*3*4];
    int lazy[maxn*12];
    void build(int pos,int l,int r){
        L[pos]=l,R[pos]=r;
        lazy[pos]=1e9+7;
        if(l==r)return;
        int mid=(l+r)/2;
        build(pos<<1,l,mid);
        build(pos<<1|1,mid+1,r);
    }
    void chg(int pos,int l,int r,int _val){
        if(L[pos]>=l&&R[pos]<=r){
            ckmn(lazy[pos],_val);
            return;
        }
        if(R[pos]<l||L[pos]>r)return;
        int mid=(L[pos]+R[pos])/2;
        if(mid>=l)chg(pos<<1,l,r,_val);
        if(mid<r)chg(pos<<1|1,l,r,_val);
    }
    int query(int pos,int x,int mn){
        if(L[pos]==x&&R[pos]==x){
            return min(mn,lazy[pos]);
        }
        if(R[pos]<x||L[pos]>x)return 1e9+7;
        int mid=(L[pos]+R[pos])/2;
        int ans=1e9+7;
        if(mid>=x)ans=query(pos<<1,x,min(mn,lazy[pos]));
        if(mid<x)ckmn(ans,query(pos<<1|1,x,min(mn,lazy[pos])));
        return ans;
    } 
}sgt1;
struct SGT2{
    int L[maxn*3*4],R[maxn*3*4];
    ll val[maxn*3*4];
    bool lazy[maxn*12];
    int cnt[maxn*12];
    void build(int pos,int l,int r){
        L[pos]=l,R[pos]=r;
        if(l==r)return;
        int mid=(l+r)/2;
        build(pos<<1,l,mid);
        build(pos<<1|1,mid+1,r);
    }
    inline int len(int pos){
        return R[pos]-L[pos]+1;
    }
    void pushup(int pos){
        val[pos]=val[pos<<1]+val[pos<<1|1];
        cnt[pos]=cnt[pos<<1]+cnt[pos<<1|1];
    }
    void pushdown(int pos){
        if(lazy[pos]){
            lazy[pos]=0;
            cnt[pos<<1]=cnt[pos<<1|1]=0;
            val[pos<<1]=val[pos<<1|1]=0;
            lazy[pos<<1]=lazy[pos<<1|1]=1;
        }
    }
    void chg(int pos,int x,int _val){
        if(L[pos]==x&&R[pos]==x){
            val[pos]+=_val;
            cnt[pos]++;
            return;
        }
        if(R[pos]<x||L[pos]>x)return;
        pushdown(pos);
        int mid=(L[pos]+R[pos])/2;
        if(mid>=x)chg(pos<<1,x,_val);
        if(mid<x)chg(pos<<1|1,x,_val);
        pushup(pos);
    }
    void reset(int pos,int l,int r){
        if(L[pos]>=l&&R[pos]<=r){
            lazy[pos]=1;
            val[pos]=0;
            cnt[pos]=0;
            return;
        }
        if(R[pos]<l||L[pos]>r)return;
        pushdown(pos);
        int mid=(L[pos]+R[pos])/2;
        if(mid>=l)reset(pos<<1,l,r);
        if(mid<r)reset(pos<<1|1,l,r);
        pushup(pos);
    }
    ll query(int pos,int l,int r){
        if(L[pos]>=l&&R[pos]<=r){
            return val[pos];
        }
        if(R[pos]<l||L[pos]>r)return 0;
        pushdown(pos);
        int mid=(L[pos]+R[pos])/2;
        ll ans=0;
        if(mid>=l)ans+=query(pos<<1,l,r);
        if(mid<r)ans+=query(pos<<1|1,l,r);
        return ans;
    }
    ll count(int pos,int l,int r){
        if(L[pos]>=l&&R[pos]<=r){
            return cnt[pos];
        }
        if(R[pos]<l||L[pos]>r)return 0;
        pushdown(pos);
        int mid=(L[pos]+R[pos])/2;
        ll ans=0;
        if(mid>=l)ans+=count(pos<<1,l,r);
        if(mid<r)ans+=count(pos<<1|1,l,r);
        return ans;
    }
}sgt2;
int ans[maxn];
map<int,int> qwql_to_qwqid;// 根据 qwq 的 l,找回 qwq 的下标
void solve(){
    scanf("%d%d",&n,&m);
    FOR(i,1,n){
        scanf("%lld%lld",&qwq[i].pos,&qwq[i].len);
        disc.add(qwq[i].pos);
        disc.add(qwq[i].pos+qwq[i].len);
    }
    FOR(i,1,m){
        scanf("%lld%lld",&wz[i].pos,&wz[i].reward);
        disc.add(wz[i].pos);
    }
    disc.init();
    sgt1.build(1,1,disc.top);
    sgt2.build(1,1,disc.top);
    FOR(i,1,n){
        qwql_to_qwqid[qwq[i].pos]=i;
        sgt1.chg(1,disc.pre(qwq[i].pos),disc.pre(qwq[i].pos+qwq[i].len),qwq[i].pos);
    //  printf("i = %d : %d %d\n",i,disc.pre(qwq[i].pos),disc.pre(qwq[i].pos+qwq[i].len));
    }
    FOR(i,1,m){
        sgt2.chg(1,disc.pre(wz[i].pos),wz[i].reward);
    //  printf("wz[%d].pos.disc = %d\n",i,disc.pre(wz[i].pos));
        int id=sgt1.query(1,disc.pre(wz[i].pos),1e9+7);
    //  printf("preid = %d\n",id);
        id=qwql_to_qwqid[id];
    //    printf("i = %d disc = %d id = %d\n",i,disc.pre(wz[i].pos),id);
        if(id>=1&&id<=n){
            int l=qwq[id].pos,r=qwq[id].pos+qwq[id].len;
            // l,r 是 qwq[id] 对应的捕食区间
            ll sum=sgt2.query(1,disc.suf(l),disc.pre(r));
            ll tmp=sgt2.count(1,disc.suf(l),disc.pre(r));
        //  printf("rew = %lld\n",sum);
    //      printf("l = %d r = %d fir sum = %lld\n",disc.suf(l),disc.pre(r),sum);
            while(tmp!=0){
        //      printf("now l = %d r = %d\n",l,r);
                ans[id]+=tmp;
                sgt2.reset(1,disc.suf(l),disc.pre(r));
                qwq[id].len+=sum;
                r=qwq[id].pos+qwq[id].len;
             //   printf("new: %d %d sum = %lld upd: %d %d %lld\n",l,r,sum,disc.suf(l),disc.pre(r),qwq[i].pos);
                sum=sgt2.query(1,disc.suf(l),disc.pre(r));
                tmp=sgt2.count(1,disc.suf(l),disc.pre(r));
                sgt1.chg(1,disc.suf(l),disc.pre(r),qwq[id].pos);

            }
        }
    }
    FOR(i,1,n){
        printf("%d %lld\n",ans[i],qwq[i].len);
    }
}
int main(){
    solve();
    return 0;
}