Loading

【题解】Atcoder Beginner Contest 451(ABC451) A~F

A - illegal

判断。

#include<bits/stdc++.h>
using namespace std;
int l;
string s;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>s;
    l=s.size();
    if(l%5) cout<<"No";
    else cout<<"Yes";
    return 0;
}

B - Personnel Change

模拟。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int cnt1[110],cnt2[110];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x,y;
        cin>>x>>y;
        cnt1[x]++,cnt2[y]++;
    }
    for(int i=1;i<=m;i++){
        cout<<cnt2[i]-cnt1[i]<<'\n';
    }
    return 0;
}

C - Understory

使用 multiset 或者 priority_queue 维护树高,以 \(O(\log n)\) 的时间插入(multiset 还需以此复杂度找到删除结束的位置),并以均摊 \(O(1)\) 的时间删除。

#include<bits/stdc++.h>
using namespace std;
int q;
multiset<int> tr;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>q;
    for(int i=1;i<=q;i++){
        int op,h;
        cin>>op>>h;
        if(op==1) tr.insert(h);
        else tr.erase(tr.begin(),tr.upper_bound(h));
        cout<<tr.size()<<'\n';
    }
    return 0;
}

D - Concat Power of 2

题意类似字符串拼接,用 \(2^k\) 拼出来长度小于等于 \(9\) 的数。

观察样例 3 发现在查询 \(10^6\) 量级时答案已经接近 \(10^9\),我们可以推测 \(\le10^9\) 的全部好整数就在 \(10^6\) 量级个。

我们可以用一个 BFS 枚举出所有的好整数,不断往后拼 \(2^k\) 得到新数加入队列。执行完发现一共有 \(1257874\) 个好整数,BFS 的搜索次数也在 \(3\times10^6\) 量级。

然后对着这大约 \(10^6\) 个好整数跑排序,输出第 \(n\) 个即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+10;
const int INF=1e9;
int n;
int p[40],tot;
int gt[N],idx;
map<int,bool> vis;
queue<int> q;
void bfs(){
    q.push(0);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<=tot;i++){
            int b=log(p[i])/log(10)+1;
            int v=u*pow(10,b)+p[i];
            if(v>INF) continue;
            if(vis[v]) continue;
            gt[++idx]=v;
            vis[v]=1;
            q.push(v);
        }
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    p[0]=1;
    while(p[tot]*2<=INF) p[tot+1]=p[tot]*2,tot++;
    cout<<'\n';
    bfs();
    sort(gt+1,gt+1+idx);
    cout<<gt[n];
    return 0;
}

E - Tree Distance

根据树的性质,我们有:对于树上三点 \(i,j,k\)\(\operatorname{dis}(i,k)+\operatorname{dis}(k,j)\ge\operatorname{dis}(i,j)\)。仅当 \(k\)\(i,j\) 的路径上,则等号成立。

那么若条件里存在 \(A_{i,k}+A_{k,j}<A_{i,j}\) 则一定无解。

但只知道这点依然是不可做的。一是暴力枚举 \(i,j,k\) 时间复杂度过高,二是不存在上述情况时也可能无解。

所以接下来我们就要想办法把最可能有解的树构造出来,再去判断这棵树是否满足所有条件。

为了方便起见我们均假设构造出来的树中 \(k\)\(i,j\) 的路径上,\((i,k)\)\((k,j)\) 为实际存在的路径且边权和分别等于 \(A_{i,k}\)\(A_{k,j}\)。倘若这时 \(\operatorname{dis}(i,k)+\operatorname{dis}(k,j)>A_{i,j}\),那么构造出来的树不合法。

此时如果可以通过把 \((i,j)\) 变成实际路径代替 \((i,k)\)\((k,j)\) 使上式不满足,则需要满足 \(A_{i,j}<A_{i,k}\)\(A_{i,j}<A_{k,j}\)

我们要构造最优的树,不会进行任何替换,也就是 \(A_{i,j}\) 一定是三者中最大的。为了使不被实际连起来的边最大,我们只需要跑最小生成树。接着跑 \(n\) 次 DFS 算出每两个点之间的距离,看看是否都等于 \(A_{i,j}\) 就可以了。

用 Kruskal 实现的时间复杂度 \(O(n^2\log n^2)\)。用 Prim 可以达到 \(O(n^2\log n)\)。均可通过此题。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3010;
int n,tot;
int g[N][N];
struct Node{
    int from,to,w;
}e[N*N];
struct eNode{
    int to,nxt,w;
}e2[N*N];
int h[N],toth;
void Add(int u,int v,int w){
    toth++;
    e2[toth].to=v;
    e2[toth].w=w;
    e2[toth].nxt=h[u];
    h[u]=toth;
}
bool cmp(Node x,Node y){
    return x.w<y.w;
}
int fa[N];
int Find(int x){
    if(fa[x]==x) return x;
    else return fa[x]=Find(fa[x]);
}
void Merge(int x,int y){
    x=Find(x),y=Find(y);
    fa[x]=y;
}
void Kruskal(){
    for(int i=1;i<=n;i++) fa[i]=i;
    int num=0;
    for(int i=1;i<=tot;i++){
        int u=e[i].from,v=e[i].to,w=e[i].w;
        int fu=Find(u),fv=Find(v);
        if(fu!=fv){
            Merge(fu,fv);
            Add(u,v,w),Add(v,u,w);
            num++;  
            if(num==n-1) return ;
        }
    }
}
int dis[N][N];
void dfs(int s,int u,int fa){
    for(int i=h[u];i;i=e2[i].nxt){
        int v=e2[i].to;
        if(v==fa) continue;
        dis[s][v]=dis[s][u]+e2[i].w;
        dfs(s,v,u);
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n-1;i++){
        for(int j=i+1;j<=n;j++){
            int w;
            cin>>w;
            e[++tot]=(Node){i,j,w};
            g[i][j]=w;
        }
    }
    sort(e+1,e+1+tot,cmp);
    Kruskal();
    for(int i=1;i<=n;i++) dfs(i,i,0);
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            if(dis[i][j]!=g[i][j]){
                cout<<"No";
                return 0;
            }
        }
    }
    cout<<"Yes";
    return 0;
}

F - Make Bipartite 3

对于确定是否为二分图,用并查集维护连通性和颜色,若一条边两端在同一连通块且同色则从此以后不再是二分图。

对于维护颜色,用带权并查集维护节点到根边数的奇偶性和每个块 权为奇数的点 的数量。连接两个连通块时启发式合并确保时间复杂度正确。输出时输出权为奇 / 偶点数量中的最小值,可以每次合并前先分别减去两个块的该值,合并后再加上大块的该值。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,q,ans;
int fa[N],siz[N],val[N];
bool col[N];
int find(int x){
    if(fa[x]==x) return x;
    int f=fa[x];
    fa[x]=find(fa[x]);
    col[x]^=col[f];
    return fa[x];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1,val[i]=1;
    while(q--){
        int u,v;
        cin>>u>>v;
        if(ans==-1){
            cout<<"-1\n";
            continue;
        }
        int fu=find(u),fv=find(v);
        if(fu==fv){
            if(col[u]==col[v]){
                ans=-1;
                cout<<"-1\n";
            }else cout<<ans<<'\n';
            continue;
        }
        if(siz[fu]>siz[fv]){
            swap(fu,fv);
            swap(u,v);
        }
        ans-=min(val[fu],siz[fu]-val[fu]);
        ans-=min(val[fv],siz[fv]-val[fv]);
        col[fu]=col[u]^col[v]^1;
        if(col[fu]) val[fu]=siz[fu]-val[fu];
        fa[fu]=fv;
        val[fv]+=val[fu];
        siz[fv]+=siz[fu];
        ans+=min(val[fv],siz[fv]-val[fv]);
        cout<<ans<<'\n';
    }
    return 0;
}
posted @ 2026-03-29 00:08  Seqfrel  阅读(85)  评论(0)    收藏  举报