2021.11.10考试总结[冲刺NOIP模拟27]

T1 开挂

因为可以随便选 \(b\) ,所以可以先对 \(a\) \(b\) 排序。不难想到最优策略是尽量把操作集中到一个点上,再把较小的 \(b\) 分配给它。

因此应让开始偏小的 \(a\) 最终尽量大。这个倒序扫描就可以实现。中间会出现若干空隙,考虑最优策略,肯定是先将数填到最近的空隙里,用栈维护可以完成。

\(code:\)

T1
#include<bits/stdc++.h>
using namespace std;

namespace IO{
   typedef long long LL; typedef long double LD;
   typedef unsigned long long ULL; typedef double DB;
   typedef pair<int,int> PII;
   #define fi first
   #define se second
   #define mpr make_pair
   #define int ULL
   #define pb push_back
   const int Mxdt=100000;
   inline char gc(){
       static char buf[Mxdt],*p1=buf,*p2=buf;
       return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
   }
   inline int read(){
       int t=0,f=0;char v=gc();
       while(v<'0')f|=(v=='-'),v=gc();
       while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
       return f?-t:t;
   }
   void write(int x,char sp){
       char ch[50]; int len=0;
       if(x<0) x=-x,putchar('-');
       do{ ch[len++]=x%10+'0'; x/=10; }while(x);
       for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
   }
   void ckmin(int& x,int y){ x=x<y?x:y; }
   void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=1000010;
int n,ans,a[NN],b[NN];
int it,top,pre;
PII stk[NN];
vector<int>op;

signed main(){
   freopen("openhook.in","r",stdin);
   freopen("openhook.out","w",stdout);
   n=read();
   for(int i=1;i<=n;i++) a[i]=read();
   for(int i=1;i<=n;i++) b[i]=read();
   sort(a+1,a+n+1); sort(b+1,b+n+1);
   pre=a[n];
   for(it=n-1;a[it]==a[n];it--)
       ++pre,op.pb(pre-a[it]);
   while(it){
       int lst=it,cnt=-1;
       if(a[it]<a[it+1]-1) stk[++top]=mpr(a[it]+1,a[it+1]-1);
       while(a[it]==a[lst]) --it,++cnt;
       while(cnt&&top){
           while(cnt&&stk[top].fi<=stk[top].se)
               --cnt,op.pb(stk[top].fi-a[lst]),++stk[top].fi;
           if(stk[top].fi>stk[top].se) --top;
       }
       while(cnt) --cnt,++pre,op.pb(pre-a[lst]);
   }
   sort(op.begin(),op.end(),[](int x,int y)->bool{return x>y;});
   for(int i=0;i<op.size();i++) ans+=op[i]*b[i+1];
   write(ans,'\n');
   return 0;
}

T2 叁仟柒佰万

发现序列中合法的 \(mex\) 值只能有一个,可以用桶找出这个 \(mex\)

考虑暴力 \(n^2\) DP,可以预处理出所有 \(mex\) 合法的区间,设 \(f_i\) 为当前最后一个区间右端点为 \(i\) 的方案数,转移时枚举右端点对应的左端点,将方案数加和。

在右端点 \(r\) 增加时,最靠右的合法左端点是单调不降的,因此预处理可以通过单调指针加桶 \(O(n)\) 完成。转移时,每个右端点的合法左端点一定是从 \(1\) 开始的一段连续点。前缀和优化可以达到 \(O(n)\)

\(code:\)

T2
# pragma GCC optimize(12)
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef long double LD;
    typedef unsigned long long ULL; typedef double DB;
    const int Mxdt=100000;
    inline char gc(){
        static char buf[Mxdt],*p1=buf,*p2=buf;
        return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
    }
    inline int read(){
        int t=0,f=0;char v=gc();
        while(v<'0')f|=(v=='-'),v=gc();
        while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
        return f?-t:t;
    }
    void write(int x,char sp){
        char ch[50]; int len=0;
        if(x<0) x=-x,putchar('-');
        do{ ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
    }
    void ckmin(int& x,int y){ x=x<y?x:y; }
    void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=37000010,mod=1e9+7;
int t,n,mex,a[NN],f[NN];
int buc[NN];
int qmod(int a){ return a<0?(a+mod):(a>=mod?a-mod:a); }
int qpow(int a,int b,int res=1){
    for(;b;b>>=1,a=1ll*a*a%mod)
        if(b&1) res=1ll*res*a%mod;
    return res;
}
void read_a(){
    f[1]=-1;
    if(n<37000000)
        for(int i=1;i<=n;i++) buc[(a[i]=read())]=1,f[i]=-1;
    else{
        int x=read(),y=read();
        for(int i=2;i<=n;i++)
            buc[(a[i]=(1ll*a[i-1]*x+y+i)&262143)]=1,f[i]=-1;
    }
    while(buc[mex]) ++mex;
}
void clear(){
    mex=0;
    for(int i=1;i<=n;i++)
        buc[i]=f[i]=buc[a[i]]=0;
}
void init(){
    if(n<37000000) for(int i=1;i<=n;i++) buc[a[i]]=0;
    else memset(buc,0,sizeof(buc));
    int it=0,now=0,pos=0;
    while(now!=mex){
        ++buc[a[++pos]];
        while(buc[now]) ++now;
    } --buc[a[pos]];
    for(int i=pos;i<=n;i++){
        ++buc[a[i]];
        while(buc[a[it+1]]>1||a[it+1]>mex)
            ++it,--buc[a[it]];
        f[i]=it;
    }
    f[0]=buc[0]=1;
}

signed main(){
    freopen("clods.in","r",stdin);
    freopen("clods.out","w",stdout);
    t=read();
    while(t--){
        n=read(); read_a();
        if(!mex){ write(qpow(2,n-1),'\n'); goto nxt; }
        init();
        for(int i=1;i<=n;i++){
            if(f[i]>=0) f[i]=buc[f[i]];
            else f[i]=0;
            buc[i]=qmod(buc[i-1]+f[i]);
        }
        write(f[n],'\n');
nxt:	clear();
    }
    return 0;
}

T3 超级加倍

一个牛B的东西: \(kruscal\) 重构树。按点权大小建树,可以满足原树上路径 \((x,y)\) 上最大或最小值在重构树上为 \(x,y\)\(lca\)

因此建出树 \(t1,t2\) ,分别为按点权递增,递减建出的重构树。那么要求的答案实际上就是满足 \(x\)\(t1\) 中为 \(y\) 祖先,在 \(t2\) 中为 \(y\) 后代的点对 \((x,y)\) 个数。本质上是个偏序问题,求出 \(t1\) 中的 \(dfs\) 序,在 \(t2\) 中用树状数组维护祖先链并查询即可。

\(code:\)

T3
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef long double LD;
    typedef unsigned long long ULL; typedef double DB;
    #define pb push_back
    const int Mxdt=100000;
    inline char gc(){
            static char buf[Mxdt],*p1=buf,*p2=buf;
            return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
    }
    inline int read(){
            int t=0,f=0;char v=gc();
            while(v<'0')f|=(v=='-'),v=gc();
            while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
            return f?-t:t;
    }
    void write(LL x,char sp){
        char ch[50]; int len=0;
        if(x<0) x=-x,putchar('-');
        do{ ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
    }
    void ckmin(int& x,int y){ x=x<y?x:y; }
    void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=2000010;
int n,idx,prt[NN],heade[NN],headx[NN],headn[NN];
int cnt,up[NN],dn[NN];
LL ans;
struct Edge{
    int to,nex;
    Edge(){}
    Edge(int t,int x){ to=t; nex=x; }
}tx[NN<<1],tn[NN<<1],te[NN<<1];
void addx(int a,int b){
    tx[++idx]=Edge(b,headx[a]); headx[a]=idx;
    tx[++idx]=Edge(a,headx[b]); headx[b]=idx;
}
void addn(int a,int b){
    tn[++idx]=Edge(b,headn[a]); headn[a]=idx;
    tn[++idx]=Edge(a,headn[b]); headn[b]=idx;
}
void adde(int a,int b){
    te[++idx]=Edge(b,heade[a]); heade[a]=idx;
    te[++idx]=Edge(a,heade[b]); heade[b]=idx;
}

namespace DSU{
    int FA[NN];
    int getf(int x){ return FA[x]==x?x:FA[x]=getf(FA[x]); }
    void merge(int x,int y){
        x=getf(x); y=getf(y);
        if(x==y) return;
        FA[y]=x;
    }
    void build(){
        idx=0;
        for(int i=1;i<=n;i++) FA[i]=i;
        for(int i=1;i<=n;i++)
            for(int j=heade[i],v=te[j].to;j;j=te[j].nex,v=te[j].to)
                if(v<i){ addx(i,getf(v)); merge(i,v); }
        idx=0;
        for(int i=1;i<=n;i++) FA[i]=i;
        for(int i=n;i>=1;i--)
            for(int j=heade[i],v=te[j].to;j;j=te[j].nex,v=te[j].to)
                if(v>i){ addn(i,getf(v)); merge(i,v); }
    }
} using namespace DSU;

namespace BIT{
    LL c[NN];
    void insert(int pos,int x){ while(pos<=n){ c[pos]+=x; pos+=pos&-pos; } }
    LL query(int pos,LL x=0){ while(pos){ x+=c[pos]; pos-=pos&-pos; } return x; }
    LL calc(int l,int r){ return query(r)-query(l-1); }
} using namespace BIT;

void dfsx(int s,int f){
    ans+=calc(up[s],dn[s]);
    insert(up[s],1);
    for(int v,i=headx[s];i;i=tx[i].nex)
        if((v=tx[i].to)!=f) dfsx(v,s);
    insert(up[s],-1);
}
void dfsn(int s,int f){
    up[s]=++cnt;
    for(int v,i=headn[s];i;i=tn[i].nex)
        if((v=tn[i].to)!=f) dfsn(v,s);
    dn[s]=cnt;
}

signed main(){
    freopen("charity.in","r",stdin);
    freopen("charity.out","w",stdout);
    n=read(); idx=read();
    for(int a,i=2;i<=n;i++)
        a=read(),adde(i,a);
    build(); dfsn(1,0); dfsx(n,0);
    write(ans,'\n');
    return 0;
}

T4 欢乐豆

完全图边极多而改动的边较少,考虑从这点入手。

没改动的点 \(i\) 到其他任一点的最短路必然是 \(a_i\)

如果把改动的边的导出子图拿出来考虑,将边看做双向边,那么会产生若干联通块。一个点到它所在联通块外一点的最短路可以看作先走到联通块内一点 \(t\) ,再通过 \(a_t\) 走到终点,因此可以对每个联通块跑最短路。

但每个联通块内除改动的边还有若干没改动的边,直接 dijkstra 依然会死。这时可以通过线段树代替优先队列完成对最短路的优化。

在线段树中维护距离最小值和最小值位置,每次取出最小值更新,并用最小值更新其他点的距离。注意这里改动和未改动的边分开考虑,一个单点修改,一个区间修改。

另外,一个点到同一联通块的距离不一定为联通块内最短路,也可能通过联通块外的点到达。注意细节即可。

\(code:\)

T4
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL; typedef long double LD;
    typedef unsigned long long ULL; typedef double DB;
    typedef pair<int,int>PII;
    #define int LL
    #define mpr make_pair
    #define pb push_back
    #define fi first
    #define se second
    const int Mxdt=100000;
    inline char gc(){
        static char buf[Mxdt],*p1=buf,*p2=buf;
        return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
    }
    inline int read(){
        int t=0,f=0;char v=gc();
        while(v<'0')f|=(v=='-'),v=gc();
        while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
        return f?-t:t;
    }
    void write(int x,char sp){
        char ch[50]; int len=0;
        if(x<0) x=-x,putchar('-');
        do{ ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
    }
    void ckmin(int& x,int y){ x=x<y?x:y; }
    void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=500010,inf=1e18;
int n,m,s,ans,a[NN];
int ic,id[NN],at[NN],dis[NN];
vector<PII>e[NN];
vector<int>p[NN];
multiset<int>S;

namespace DSU{
    int ff[NN],siz[NN];
    int getf(int x){ return ff[x]==x?x:ff[x]=getf(ff[x]); }
    void merge(int x,int y){
        x=getf(x); y=getf(y);
        if(x==y) return;
        siz[x]+=siz[y];
        ff[y]=x;
    }
} using namespace DSU;

namespace Segment_Tree{
    #define ld rt<<1
    #define rd (rt<<1)|1
    int mn[NN<<2],ps[NN<<2],lz[NN<<2];
    bool tg[NN<<2];
    void pushup(int rt){
        mn[rt]=min(mn[ld],mn[rd]);
        if(mn[rt]==mn[ld]&&!tg[ld]) ps[rt]=ps[ld];
        else ps[rt]=ps[rd];
        tg[rt]=tg[ld]&tg[rd];
    }
    void pushdown(int rt){
        if(lz[rt]==inf) return;
        if(!tg[ld]) ckmin(mn[ld],lz[rt]),ckmin(lz[ld],lz[rt]);
        if(!tg[rd]) ckmin(mn[rd],lz[rt]),ckmin(lz[rd],lz[rt]);
        lz[rt]=inf;
    }
    void build(int rt=1,int l=1,int r=s){
        mn[rt]=lz[rt]=inf; tg[rt]=0; ps[rt]=l;
        if(l==r) return; int mid=l+r>>1;
        build(ld,l,mid); build(rd,mid+1,r);
    }
    void delet(int pos,int rt=1,int l=1,int r=s){
        if(l==r){ mn[rt]=inf; tg[rt]=1; return; }
        pushdown(rt); int mid=l+r>>1;
        if(pos<=mid) delet(pos,ld,l,mid);
        else delet(pos,rd,mid+1,r);
        pushup(rt);
    }
    void modify(int opl,int opr,int val,int rt=1,int l=1,int r=s){
        if(tg[rt]||opl>opr) return;
        if(l>=opl&&r<=opr){ ckmin(mn[rt],val); ckmin(lz[rt],val); return; }
        pushdown(rt); int mid=l+r>>1;
        if(opl<=mid) modify(opl,opr,val,ld,l,mid);
        if(opr>mid) modify(opl,opr,val,rd,mid+1,r);
        pushup(rt);
    }
} using namespace Segment_Tree;

void dijkstra(int i,int u){
    build(); modify(at[u],at[u],0); dis[at[u]]=0;
    while(mn[1]!=inf){
        int x=ps[1],y=mn[1],lst=1;
        dis[x]=y; delet(x);
        x=p[i][x-1];
        for(auto vl:e[x]){
            modify(lst,at[vl.fi]-1,y+a[x]);
            modify(at[vl.fi],at[vl.fi],y+vl.se);
            lst=at[vl.fi]+1;
        }
        modify(lst,s,y+a[x]);
    }
}

signed main(){
    freopen("happybean.in","r",stdin);
    freopen("happybean.out","w",stdout);
    n=read(); m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),ff[i]=i,siz[i]=1,S.insert(a[i]);
    for(int a,b,c,i=1;i<=m;i++){
        a=read(); b=read(); c=read();
        e[a].pb(mpr(b,c)); merge(a,b);
    }
    for(int i=1;i<=n;i++){
        sort(e[i].begin(),e[i].end());
        if(ff[i]==i&&siz[i]>1) id[i]=++ic;
    }
    for(int i=1;i<=n;i++)
        if(siz[getf(i)]>1) p[id[getf(i)]].pb(i);
        else ans+=(n-1)*a[i];
    for(int i=1;i<=ic;i++){
        s=p[i].size();
        for(int j=0;j<s;j++){
            at[p[i][j]]=j+1;
            S.erase(S.find(a[p[i][j]]));
        }
        int tmp=S.size()?(*S.begin()):inf;
        for(int u:p[i]){
            dijkstra(i,u);
            for(int j=1;j<=s;j++) ans+=min(dis[j],tmp+a[u]);
            int mnn=inf;
            for(int j=1;j<=s;j++) ckmin(mnn,dis[j]+a[p[i][j-1]]);
            ans+=mnn*(n-s);
        }
        for(int j:p[i]) S.insert(a[j]);
    }
    write(ans,'\n');
    return 0;
}
posted @ 2021-11-10 19:17  keen_z  阅读(118)  评论(0)    收藏  举报