【日常摸鱼】牛客挑战赛1

【日常摸鱼】牛客挑战赛1

前言

寒假了,终于可以练acm(颓废)了。。。

A Treepath

水题略了

B Xorto

水题略了

C MMSet2

链接

https://ac.nowcoder.com/acm/contest/15/C

题意

给定一棵n个节点的树,点编号为1…n。
Q次询问,每次给出一个点集S,定义f(u)表示S中离u最远的点到u的距离,询问min(f(u))。
\(n\leq 3*{10}^5,\sum{|S|} \leq {10}^6\)

题解

每次给出一个点集来询问,这个形式很像虚树。
首先我们找到包含整个点集S的最小联通块,显然这个联通块把不是lca的点都省略掉就是虚树了。
容易发现我们要找的min(f(u))的u一定在这个联通块上(显然)。
更进一步,这个点u一定是这个联通块的某一条直径的中间点,即这个联通块的中心(根据树的直径和中心的一系列知识)。
然后我们要做的就是算出这个联通块的直径长度,答案一定是(直径长度+1)/2。
算直径长度的话,虚树肯定是可以的。
也可以直接找到点集S的最深点,然后跟其他S里的点算距离(最深点一定是某条直径的一端)。
两种方法都是询问点数*log的。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
const int INF=1e9;
int n,m;
vector<int> r[N];
int dep[N],dfn[N];
int f[N][20];
void dfs(int x){
    dfn[x]=++m;
    for(int i=1;i<20;++i) 
        f[x][i]=f[f[x][i-1]][i-1];
    for(int i=0;i<r[x].size();++i){
        if(r[x][i]!=f[x][0]){
            f[r[x][i]][0]=x;
            dep[r[x][i]]=dep[x]+1;
            dfs(r[x][i]);
        }
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=19;i>=0;--i){
        if(d&(1<<i)) x=f[x][i];
    }
    if(x==y) return x;
    for(int i=19;i>=0;--i){
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int dis(int x,int y){
    return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int K;
int a[N];
int main(){
    int u,v,ans;
    scanf("%d",&n);
    for(int i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        r[u].push_back(v);
        r[v].push_back(u);
    }
    m=0;dfs(1);
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&K);
        for(int i=1;i<=K;++i){
            scanf("%d",&a[i]);
        }
        if(K<=1){
            puts("0");continue;
        }
        u=1;ans=0;
        for(int i=2;i<=K;++i){
            if(dep[a[i]]>dep[a[u]]) u=i;
        }
        for(int i=1;i<=K;++i){
            ans=max(ans,dis(a[i],a[u]));
        }
        ans=(ans+1)>>1;
        printf("%d\n",ans);
    }
    return 0;
} 

D Color

链接

https://ac.nowcoder.com/acm/contest/15/D

题意

给一个没有重边\(n\)个点\(m\)条边的二分图, 要求给边染色. 有公共点的边不能同色. 问最少用多少种颜色, 并任意构造一组方案.
\(n\leq 1001,m\leq 2001\)

题解

颜色数最小肯定是度数最多的那个点的度数,设为C。。
然后用贪心的策略,按照颜色做C次匈牙利算法,每次尽量让剩余度数大的点优先匹配。
然后就做完了。。。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
const int INF=1e9;
int n,m,maxc=0;
int mp[1050][1050];
int l[2050],r[2050];
int du[1050];
int q[1050];
bool vis[1050];
int mark[1050];
vector<int> ve[1050];
bool cmp(int x,int y){
    return du[x]>du[y];
}
bool dfs(int k){
    int j;
    for(int i=0;i<ve[k].size();++i){
        j=ve[k][i];
        if(!vis[j]){
            vis[j]=1;
            if(mark[j]==0||dfs(mark[j])){
                mark[j]=k;
                mark[k]=j;
                return 1;
            }
        }
    }
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        scanf("%d%d",&l[i],&r[i]);
        ++du[l[i]];++du[r[i]];
    }
    for(int i=1;i<=n;++i) maxc=max(maxc,du[i]);
    for(int C=1;C<=maxc;++C){
        for(int i=1;i<=n;++i)  {
            ve[i].clear();
            q[i]=i;
        }
        for(int i=1;i<=m;++i){
            if(!mp[l[i]][r[i]]){
                ve[l[i]].push_back(r[i]);
                ve[r[i]].push_back(l[i]);
            }
        }
        sort(q+1,q+1+n,cmp);
        memset(mark,0,sizeof(mark));
        for(int i=1;i<=n;++i){
            if(!mark[q[i]]){
                memset(vis,0,sizeof(vis));
                dfs(q[i]);
            }
        }
        for(int i=1;i<=n;++i){
            if(mark[i]){
                mp[i][mark[i]]=C;
                --du[i];
            }
        }
    }
    printf("%d\n",maxc);
    for(int i=1;i<=m;++i){
        printf("%d\n",mp[l[i]][r[i]]);
    }
    return 0;
} 

E Cut

链接

https://ac.nowcoder.com/acm/contest/15/E

题意

给定一个\(n\)个点\(m\)条边的无向简单图(即无重边无自环). 每条边都有一个权值. 这个图的一个鸽, 指的是将它的点集划分为两个不重不漏的集合S和T. 这个鸽的权值, 是所有两个端点分别属于S和T的边的权值的异或和(即, S内部的边和T内部的边都不算). 现在问这个图的鸽的所有可能权值的和是多少. 由于这个数很大, 只需要输出前9位, 不足9位则全部输出.
\(n\leq 100000,m\leq 200000\)

题解

异或+割的题目有一个关键的性质:
我们先把所有点的点值赋为所有跟它相连的边的权值异或和,
那么任意一个割的异或和都可以表示为这个割的某一边的所有点的点值的异或和。
因为在同一点集的边会被异或两次,在另一点集的边不会被异或到。
然后我们赋完点值后,所有的割的可能值都能用点值来表示了。
问所有可能的割值的总和,我们可以先做线性基,能表示出的数都不变。
然后按每一位来考虑。如果某一位可能存在,那么在所有可能中,这位值为1的次数就是总次数的1/2(这个性质和清华集训 玛里苟斯 那题很像)。
于是答案其实不会超过longlong范围,直接算就行了。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=3e5+10;
const LL lim=1e9;
int n,m;
int w[N];
LL bin[70];
LL f[70];
int main(){
    bin[0]=1;for(int i=1;i<=62;++i)bin[i]=bin[i-1]<<1;
    int u,v,x;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&x);
        w[u]^=x;w[v]^=x;
    }
    if(n<2) {
        puts("0");
        return 0;
    }
    for(int i=1;i<=n;++i){
        for(int j=30;j>=0;--j){
            if(w[i]&bin[j]){
                if(!f[j]){
                    f[j]=w[i];
                    break;
                }
                else{
                    w[i]^=f[j];
                }
            }
        }
    }
    int num=0;
    for(int i=0;i<=30;++i) if(f[i]) ++num;
    LL ans=0;
    for(int i=0;i<=30;++i){
        bool flag=0;
        for(int j=0;j<=30;++j) if(f[j]&bin[i]) flag=1;
        if(flag){
            ans+=bin[i+num-1];
        } 
    }
    while(ans>lim) ans=ans/(LL)10;
    printf("%lld\n",ans);
    return 0;
} 
posted @ 2021-02-03 00:50  Iscream-2001  阅读(48)  评论(0编辑  收藏  举报
/* */