• 2021.2 月赛补题A-C


    2021.2 月赛补题A-C

    我一直觉得EOJ的题解写的可以,所以思路和题解基本参考Eoj官方题解。

    连接:https://acm.ecnu.edu.cn/blog/entry/1089/

    这场真不愧是oi爷出题,我还没摸键盘,只是在地铁上看了看A,知道是个lca,没有想出来dfs序,就开始自闭了。

    但我觉得这场题目很好,值得一写。

    A.
    /*
      首先要知道两个结论:
      1.就是n个点的lca就是dfs序差最大的一组数的lca,所以先要对这5个点进行dfs序排序,求出5个点的lca
      2.其余的点u对已经有的点合并:u对已存在的点求lca,深度最深的lca就是离这个点u最近的公共节点
      复杂度O(10qlogn)
    */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define debug(x) cout<<#x<<':'<<x<<endl;
    #define u first
    #define w second
    const int maxn=5e4+60;
    int n,q,u,v,f,cnt;
    int father[maxn][32],cost[maxn][32],depth[maxn],a[6],dis[maxn],ls[maxn],rs[maxn];
    vector<pair<int,int>>G[maxn];
    void dfs(int root,int b){
        father[root][0]=b;ls[root]=++cnt;
        depth[root]=depth[father[root][0]]+1;
        for(int i=1;i<32;i++){
            father[root][i]=father[father[root][i-1]][i-1];
        }
        int sz=G[root].size();
        for(int i=0;i<sz;i++){
            if(G[root][i].u==b) continue;
            dis[G[root][i].u]=dis[root]+G[root][i].w;
            dfs(G[root][i].u,root);
        }
        rs[root]=cnt;
    }
    int lca(int x,int y){
        if(depth[x]<depth[y]) swap(x,y);
        for(int i=31;i>=0;i--){//无限逼近接近二分,把x,y调整到同一深度
            if(depth[father[x][i]]>=depth[y])x=father[x][i];
        }
        if(x==y) return x;
        for(int i=31;i>=0;i--){
            if(father[x][i]!=father[y][i]){
                x=father[x][i];
                y=father[y][i];
            }
        }
        return father[x][0];
    }
    bool cmp(int a,int b){
        return ls[a]<ls[b];
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) G[i].clear();
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&f);u++,v++;//确保节点数从1开始
            G[v].push_back({u,f}); G[u].push_back({v,f});
        }
        dfs(1,0);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
            for(int j=1;j<=5;j++)
            scanf("%d",&a[j]),a[j]++;
            sort(a+1,a+6,cmp);
            
            int qwq=lca(a[1],a[5]);
            int ans=dis[a[1]]+dis[a[5]]-2*dis[qwq];
    
            swap(a[2],a[5]);
            for(int j=3;j<=5;j++){//并到之前的点上
                int bst_lca=0;
                for(int k=1;k<j;++k){
                    int now_lca=lca(a[j],a[k]);
                    if(bst_lca==0||depth[now_lca]>depth[bst_lca]){//找一个深度最深的lca
                        bst_lca=now_lca;
                    }
                }
                ans+=dis[a[j]]-dis[bst_lca];
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    
    B.
    /*
    对每个人起终点bfs求单源最短路,然后算通过每点的概率
    pre数组表示到该点最短路的数量,1表示从起点出发,0表示从终点出发
    复杂度O(n*k)
    */
    
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+100;
    const int inf =0x3f3f3f3f;
    double sum[maxn];
    vector<int>G[maxn];
    int n,m,u,v,k;
    int a[maxn],b[maxn],pre[maxn][2],d[maxn][2],vis[maxn];
    #define debug(x) cout<<#x<<':'<<x<<endl;
    void bfs(int s,int f){
        queue<int>q;
        for(int i=1;i<=n;i++) vis[i]=0,d[i][f]=inf;
        q.push(s);d[s][f]=0;pre[s][f]=1;
        while(!q.empty()){
            int u=q.front();
            q.pop();
            if(vis[u]) continue;
            else vis[u]=1;
    
            for(auto v: G[u]){
                if(vis[v]) continue;
                if(d[v][f]>d[u][f]+1){
                    d[v][f]=d[u][f]+1;
                    pre[v][f]=pre[u][f];
                    q.push(v);
                }else if(d[v][f]==d[u][f]+1){
                    pre[v][f]+=pre[u][f];
                }
            }
        }
    }
    int main(){
        scanf("%d%d",&n,&m);
        while(m--){
            scanf("%d%d",&u,&v);u++,v++;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        scanf("%d",&k);
        for(int i=1;i<=k;i++){
            scanf("%d%d",&a[i],&b[i]);a[i]++,b[i]++;
            for(int i=1;i<=n;i++) pre[i][1]=pre[i][0];
            bfs(a[i],1),bfs(b[i],0);
            for(int j=1;j<=n;j++){
                if(j==a[i]||j==b[i]) sum[j]+=1;//起点和终点的概率是100%
                /*经过该点的概率=起点到该点的方法数*该点到终点的方法数/起点到终点的方法数*/
                else if(d[j][1]+d[j][0]==d[b[i]][1]) sum[j]+=1.0*pre[j][1]*pre[j][0]/pre[b[i]][1];
            }
        }
        int ans=1;
        double dmax=0;
        for(int i=1;i<=n;i++){
            if(sum[i]>dmax)dmax=sum[i],ans=i;
        }
        printf("%d",ans-1);
    }
    
    
    C.
    /*
    这题我一开始就觉得是一道lazy线段树,但以下的代码是根据EOJ说的题解补的,我觉得这个写法更简单
    核心思想是:牢牢把握住每段区间最小的值,也就意味着对于当前这个区间[mini[w].first,mini[w].second]必然有i,
    而绝没有比它小的数,那就意味着可以随意取这个区间之内的一个位置来放这个值
    */
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e5+100;
    int ans[maxn],vis[maxn],n,q;
    //#define debug(x) cout<<#x<<':'<<x<<endl;
    pair<int,int>mini[maxn],maxi[maxn];
    
    void deal(int n) {
        for (int i = 1; i <= n; ++i) cout << -1 << ' ';
        cout << endl;
        exit(0);//这个写法很好,很干净,值得借鉴
    }
    int main(){
        set<int>s;
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++){
            mini[i]={1,n},maxi[i]={n,1};
            s.insert(i);
        }
        while(q--){
            int l,r,w;
            scanf("%d%d%d",&l,&r,&w);l++,r++,w++;
            mini[w].first=max(mini[w].first,l);
            mini[w].second=min(mini[w].second,r);
            maxi[w].first=min(maxi[w].first,l);
            maxi[w].second=max(maxi[w].second,r);
            vis[w]=1;
        }
        queue<int>Q;
        for(int i=n;i>=1;i--){//枚举值找位置
            if(!vis[i])Q.push(i);
            else{
                /* 关键在于 ,[mini[w].first,min[w].second]区间里绝不可能有比他更小的值*/
                auto to=s.upper_bound(mini[i].second);--to;
                //if (to == s.end() or *to != mini[i].second) --to;//没找到
                if (*to < mini[i].first)deal(n);//在set中找不到符合要求的位置
                ans[*to] = i;//否则这个位置是值,因为可以随意指定,
                s.erase(to);
                int l = maxi[i].first, r = maxi[i].second;//对于这个区间中剩下的位置把比当前的数大的数安排进去
                auto st=s.lower_bound(l),ed = s.upper_bound(r);
                /* 这里值得注意的是,这些位置的数必然是大于当前的i,如果位置多于值,就说明构造不出来*/
                for (auto it = st; it != ed; ++it) {//枚举这个位置区间,把值安排进这个区间里
                    if ((int)Q.size()==0) deal(n);
                    ans[*it] = Q.front(), Q.pop();
                }
                s.erase(st, ed);
            }
        }
        
       for (auto to : s) {//把Q中剩余的数安排去处//非常容易忽视的细节,一定要写
            if ((int)Q.size()==0){ deal(n);}
            ans[to] = Q.front();Q.pop();
        }
        
        for (int i = 1; i <= n; ++i) {
            cout << ans[i] - 1 << " ";
        }
        
        //system("pause");
        return 0;
    }
    
  • 相关阅读:
    SpringCloud
    Springboot笔记
    SpringMVC学习随笔
    Spring中事务的理解
    Spring中Aop的理解
    FLutter 各种项目的目录创建
    flutter 打包Android 创建签名证书
    flutter web网站 第一次访问首屏页 空白卡顿 再显示
    flutter--报错--Flutter 调试时卡在 Installing buildappoutputsapkapp.apk
    flutter 顶部状态栏 底部栏 显示和隐藏
  • 原文地址:https://www.cnblogs.com/zx0710/p/14387014.html
Copyright © 2020-2023  润新知