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\n",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;
}
posted @ 2021-02-07 22:41  zx0710  阅读(65)  评论(0编辑  收藏  举报