AtCoder Beginner Contest 369 補題記錄(A~G)

yhbqwq發表於2024-08-31

A

const int N=1000100;
int a[N];
signed main(){
    int x,y;cin>>x>>y;
    if(x==y)cout<<"1\n";
    else if(x%2==y%2)cout<<"3\n";
    else cout<<"2\n";
}

B

const int N=1000100;
int a[N];
signed main(){
    int n;cin>>n;
    int cnt=0;
    VI l,r;
    F(i,1,n){
        int x;char o;cin>>x>>o;
        if(o=='L')l.eb(x);else r.eb(x);
    }
    F(i,1,(int)l.size()-1)cnt+=abs(l[i]-l[i-1]);
    F(i,1,(int)r.size()-1)cnt+=abs(r[i]-r[i-1]);
    cout<<cnt<<'\n';
}

C

發現滿足條件的合法連續子序列 \((L,R)\) 必然可以用 \(K\) 個二元組 \((A_1,B_1)\)\((A_2,B_2)\)\(\ldots\)\((A_k,B_k)\) 來表示,其中任意一個合法的連續子序列 \((L,R)\) 均滿足 \(\exists\ i\in[1,k]\cap\textbf{N}_+\)\(A_i\le L\le R\le B_i\)。然後還可以發現 \(\forall\ i,j\in [1,K],\ i<j\)\(B_i-A_j\ {\color{red}{=}}\ 1\)。因此答案即為 \(-K+1+\sum\limits_{i=1}^K\frac{(B_i-A_i+1)(B_i-A_i)}{2}\),雙指標滑動找出所有滿足條件的 \((A_i,B_i)\) 二元組,時間複雜度為 \(O(n)\)

const int N=1000100;
int a[N];
signed main(){
    int n;cin>>n;
    F(i,1,n)cin>>a[i];
    int cnt=0;
    int l=1,r=1;
    while(l<=n&&r<=n){
        while(r<=n&&(r-l<=1||a[r]-a[r-1]==a[r-1]-a[r-2]))++r;
        --r;
        int len=r-l+1;cnt+=len*(len+1)/2;
        --cnt;
        l=r;
        if(r==n)break;
    }
    cout<<cnt+1<<'\n';
}

D

\(f_{i,j}\) 表示當前前 \(i\) 只怪物擊敗了 \(j\) 只怪物最大的貢獻,可以發現 \(j\) 對答案的影響只和 \(j\bmod 2\) 的值有關係,因此設 \(f_{i,0/1}\) 表示當前前 \(i\) 只怪物擊敗了 \(j\) 只怪物的最大貢獻,顯然有:

  • \(f_{i,0}=\max(f_{i-1,0},f_{i-1,1}+2a_i)\)
  • \(f_{i,1}=\max(f_{i-1,1},f_{i-1,0}+a_i)\)

初始條件為 \(f_{0,0}=0\)\(f_{0,1}=-\infin\)

時間複雜度為 \(O(n)\)

const int N=1000100;
int a[N];
int f[N][2];
//f[i][j] 表示當前前 i 只怪物擊敗了 0/1 奇數/偶數怪物
signed main(){
    int n;cin>>n;
    F(i,1,n)cin>>a[i];
    f[0][1]=-1e18;
    F(i,1,n){
        f[i][0]=f[i-1][0],f[i][1]=f[i-1][1];
        f[i][0]=max({f[i][0],f[i-1][1]+a[i]+a[i]});
        f[i][1]=max({f[i][1],f[i-1][0]+a[i]});
    }
    cout<<max({f[n][0],f[n][1]})<<'\n';
}

E

考慮先跑 \(n\) 遍 dijkstra 求出兩兩點之間的最短路,然後對於 \(Q\) 組詢問,考慮列舉每一個橋樑的順序,然後列舉每一個橋樑的走向,暴力計算對答案的貢獻即可。時間複雜度為 \(O(能過)\)

const int N=1000100;
VII z[N];
struct qwq{
    int u,v,w;
};
struct no{
    int u,d;
    bool operator<(const no&r)const{
        return d>r.d;
    }
};
qwq ed[N];
int mp[2010][2010];
void dijk(int s){
    priority_queue<no>q;
    q.push({s,0});mp[s][s]=0;
    while(q.size()){
        auto t=q.top();q.pop();
        if(mp[s][t.u]>=t.d)
            for(auto &[g,w]:z[t.u])
                if(mp[s][g]>mp[s][t.u]+w)q.push({g,mp[s][g]=mp[s][t.u]+w});
    }
}
signed main(){
    int n,m;cin>>n>>m;
    F(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        ed[i]={u,v,w};
        addew(u,v,w);
        addew(v,u,w);
    }
    memset(mp,1,sizeof mp);
    F(i,1,2005)
        dijk(i);
    int q;cin>>q;
    while(q--){
        int k;cin>>k;
        int x[10];F(i,0,k-1)cin>>x[i];
        sort(x,x+k);
        int mi=1e18;
        do{
            //列舉每一座橋是往哪個方向走的
            F(i,0,(1ll<<k)-1){
                int cost=0;
                //x>>i&1==1 : 選擇u[i]-->v[i]
                //即 ed[x[i]].u-->x[i]+n-->ed[x[i]].v
                //x>>i&1==0 : 選擇v[i]-->u[i]
                //即 ed[x[i]].v-->x[i]+n+n-->ed[x[i]].u
                VI bridge;
                F(j,0,k-1){
                    if(i>>j&1)bridge.eb(ed[x[j]].u),bridge.eb(ed[x[j]].v),cost+=ed[x[j]].w;
                    else bridge.eb(ed[x[j]].v),bridge.eb(ed[x[j]].u),cost+=ed[x[j]].w;
                }
                cost+=mp[1][bridge[0]]+mp[bridge.back()][n];
                F(i,2,(int)bridge.size()-1){
                    cost+=mp[bridge[i-1]][bridge[i]];
                    ++i;
                    if(cost>3e18)break;
                }
                mi=min(mi,cost);
            }
        }while(next_permutation(x,x+k));
        cout<<mi<<'\n';
    }
}

F

二維偏序套路的對 \(x\) 為第一關鍵字 \(y\) 為第二關鍵字均從小到大排序,則此時 \(x\) 對答案毫無貢獻,只需要考慮 \(y\) 的貢獻,變為 LIS 板子。因為要輸出路徑所以再設 \(g_i\) 表示以 \(i\) 結尾的最大答案是從 \(g_i\) 轉移過來的,倒序遍歷答案即可。時間複雜度為 \(O(n\log n)\) 瓶頸在於線段樹維護最大值和最大值所處的位置。


const int N=600100;
PII z[N];
int f[N];
#define x first
#define y second
//可愛班花yhb天下第一可愛qwq
namespace yhb{
struct qwq{
    int l,r,mx,id;
    void init(int p){
        l=r=p;mx=id=0;
    }
}z[N<<2];
qwq operator+(const qwq&l,const qwq&r){
    qwq t;
    t.l=l.l,t.r=r.r;
    if(l.mx<=r.mx)t.mx=r.mx,t.id=r.id;
    else t.mx=l.mx,t.id=l.id;
    return t;
}
void build(int l,int r,int rt){
    if(l==r)
        return z[rt].init(l);
    int mid=l+r>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    z[rt]=z[rt<<1]+z[rt<<1|1];
}
void modify(int l,int r,int rt,int p,int v,int id){
    if(l==r){
        if(z[rt].mx<v){
            z[rt].mx=v;
            z[rt].id=id;
        }
        return;
    }
    int mid=l+r>>1;
    if(p<=mid)modify(l,mid,rt<<1,p,v,id);
    else modify(mid+1,r,rt<<1|1,p,v,id);
    z[rt]=z[rt<<1]+z[rt<<1|1];
}
qwq query(int l,int r,int rt,int ll,int rr){
    if(ll<=l&&r<=rr)
        return z[rt];
    int mid=l+r>>1;
    if(ll<=mid&&mid<rr)
        return query(l,mid,rt<<1,ll,rr)+query(mid+1,r,rt<<1|1,ll,rr);
    if(ll<=mid)
        return query(l,mid,rt<<1,ll,rr);
    return query(mid+1,r,rt<<1|1,ll,rr);
}
}
#define ro 0,200001,1
int g[N];
signed main(){
    set<PII>se;
    int n,m,k;cin>>n>>m>>k;
    F(i,1,k){
        int x,y;cin>>x>>y;
        se.insert({x,y});
        z[i]={x,y};
    }
    sort(z+1,z+k+1);
    f[0]=0;
    yhb::build(ro);
    F(i,1,k){
        f[i]=1;
        auto tk=yhb::query(ro,1,z[i].second);
        f[i]=max(f[i],tk.mx+1);
        g[i]=tk.id;
        yhb::modify(ro,z[i].second,f[i],i);
    }
    cout<<*max_element(f+1,f+k+1)<<'\n';
    VII pai;
    pai.eb(1,1);
    VII t;
    int la=-1,mx=*max_element(f+1,f+k+1);
    F(i,1,k)if(mx==f[i])la=i;
    while(la){
        t.eb(z[la]);
        la=g[la];
    }
    reverse(rng(t));
    for(auto &x:t)pai.eb(x);
    pai.eb(n,m);
    F(i,1,pai.size()-1){
        int dx=pai[i].first-pai[i-1].first;
        int dy=pai[i].second-pai[i-1].second;
        F(i,0,dx-1)cout<<"D";
        F(i,0,dy-1)cout<<"R";
    }
    cout<<'\n';
}

G

長剖板子。容易發現一定只會選不同的葉子結點才能對答案產生最大的貢獻。因此暴力長剖設 \(g_i\) 表示從 \(Top_i\)\(i\) 結點所有遍的權值的和,其中 \(Top_i\) 表示 \(i\) 所屬長鏈的頂端元素編號。記錄所有可能對答案產生貢獻的葉子結點,把所有的葉子結點的 \(g_i\) 從大到小排序然後求一遍字首和即可。時間複雜度為 \(O(n\log n)\),使用基數排序可以做到 \(O(n)\)

const int N=600100;
VII z[N];
int a[N],vis[N],deg[N],son[N],val[N];
void dfs_(int u,int fa){
    for(auto &[v,w]:z[u])
        if(v!=fa){
            a[v]=w;
            dfs_(v,u);
        }
}
void dfs(int u,int fa){
    for(auto &[v,w]:z[u])
        if(v!=fa){
            dfs(v,u);
            if(val[v]>val[son[u]])son[u]=v;
        }
    val[u]=val[son[u]]+a[u];
}
int g[N];
signed main(){
    int n;cin>>n;
    F(i,1,n-1){
        int u,v,w;cin>>u>>v>>w;
        Addew(u,v,w);
        ++deg[u],++deg[v];
    }
    dfs_(1,0);
    dfs(1,0);
    VI v;
    F(i,1,n)vis[son[i]]=1;
    F(i,1,n)if(!vis[i])v.eb(val[i]);
    sort(rng(v),greater<int>());
    int s=0;
    F(i,0,n-1){
        if(i<=min(761145141413ll,(int)v.size()-1))s+=v[i];
        cout<<s*2<<'\n';
    }
}

相關文章