牛客练习赛39

B题:https://ac.nowcoder.com/acm/contest/368/B

有一棵n个节点的二叉树,1为根节点,每个节点有一个值wi。现在要选出尽量多的点。

对于任意一棵子树,都要满足:

如果选了根节点的话,在这棵子树内选的其他的点都要比根节点的值

如果在左子树选了一个点,在右子树中选的其他点要比它

题解:dfs序+LIS。根据题意可知首先dfs按照根,右,左的顺序求出该树的序列,再按照点的权值,求出该序列的最长上升子序列。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int q[100010];
int w[100010];
int l[100010],r[100010];
int n;
int tot;
int dp[100010];
void dfs(int x)
{
    if(!x) return ;
    q[++tot]=x;
    dfs(r[x]);
    dfs(l[x]);
}
int search(int num,int low ,int high){
    int mid;
    while(low<=high){
        mid=(low+high)>>1;
        if(num>=dp[mid]) low =mid+1;
        else high=mid-1;
    }
    return low;
}
int DP(int k)
{
    int i,len,pos;
    dp[1]=q[1];
    len=1;
    for(i=2;i<=k;i++){
        if(q[i]>=dp[len]){
            len+=1;
            dp[len]=q[i];
        }
        else {
            pos=search(q[i],1,len);
            dp[pos]=q[i];
        }
    }
    return len;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%d %d",&l[i],&r[i]);
    }
    dfs(1);
    for(int i=1;i<=n;i++) q[i]=w[q[i]];
    int ans=0;
    ans=DP(n);
    printf("%d\n",ans);
    return 0;
}

C题:https://ac.nowcoder.com/acm/contest/368/C

现在一共有n天,第i天如果有流星雨的话,会有w_i颗流星雨。

第i天有流星雨的概率是p_i

如果第一天有流星雨了,那么第二天有流星雨的可能性是,否则是p_2。相应的,如果第天有流星雨,第i天有流星雨的可能性是,否则是p_i

求n天后,流星雨颗数的期望。

题解:概率dp,设dp[i]为第i天下流星雨的概率,由题意可知,dp[i]=dp[i-1]*(P+p[i])+(1-dp[i-1])*p[i]。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long LL;
#define mod 1000000007
LL binarypow(LL a,LL b,LL m) {
    LL ans=1;
    while(b>0){
        if(b&1){
            ans=ans*a%m;
        }
        a=a*a%m;
        b>>=1;
    }
    return ans;
}
int n;
ll a,b;
ll x[100010],y[100010],w[100010];
ll dp[100010];
ll P,ans=0;
ll p[100010];
int main()
{
    scanf("%d %lld %lld",&n,&a,&b);
    for(int i=1;i<=n;i++){
        scanf("%lld",&w[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld %lld",&x[i],&y[i]);
        p[i]=x[i]*binarypow(y[i],mod-2,mod)%mod;
    }
    P=a*binarypow(b,mod-2,mod)%mod;
    dp[1]=p[1];
    ans=p[1]*w[1]%mod;
    for(int i=2;i<=n;i++){
        dp[i]=0;
        dp[i]+=dp[i-1]*(P+p[i])%mod+(mod+1-dp[i-1])*p[i]%mod;
        dp[i]%=mod;
        ans+=(dp[i]*w[i]%mod);
        ans%=mod;
    }
    printf("%lld\n",ans);
    return 0;
}

D题:https://ac.nowcoder.com/acm/contest/368/D

小T有n个点,每个点可能是黑色的,可能是白色的。
小T对这张图的定义了白连通块和黑连通块:
白连通块:图中一个点集V,若满足所有点都是白点,并且V中任意两点都可以只经过V中的点互相到达,则称V中的点构成了一个白连通块。

黑连通块:类似白连通块的定义。

小T对这n个点m次操作。
1、在两个点之间连一条边。
2、询问白(黑)连通块个数。
3、给出x,y两个点,保证同色(为了方便描述,x,y都是白点,黑色同理)。询问存在多少个黑点,将它改变颜色后,x,y所在的白连通块会合并为一个。如果x,y已经在一个白连通块内了,输出-1。(注意:这里不会对点的颜色改变,只统计个数)

题解:bitset+并查集。对于询问2,可以在每次加边的过程中判断该次加边能否使连通块减少,询问3,可以用bitset来维护该两点所属的连通块所连了哪些异色点,通过与操作即可计算出这两个集合里有多少相同的1。

#include <bits/stdc++.h>
using namespace std;
#define maxn 50010
bitset<maxn> f[maxn],res;
int n,m;
int a[maxn],fa[maxn];
int cnt1,cnt2;
int findparent(int a){
    int r=a;
    while(r!=fa[r]){
        r=fa[r];
    }
    while(a!=fa[a]){
        int j=fa[a];
        fa[a]=r;
        a=j;
    }
    return r;
     
}
 
void addedge(int x,int y)
{
    int fx=findparent(x);
    int fy=findparent(y);
    if(a[x]==a[y]){
        if(fx!=fy){
            fa[fx]=fy;
            f[fy]|=f[fx];//注意合并这两个集合,因为它们要变成同一个连通块了
            if(a[x]==1){
                cnt1--;
            }
            else cnt2--;
        }
    }
    else{
        f[fx].set(y);//不是同色点,则用bitset来记录他们各自连接了哪个位置的异色点
        f[fy].set(x);
    }
}
void query1(int x)
{
    if(x==0){
        printf("%d\n",cnt2);
    }
    else {
        printf("%d\n",cnt1);
    }
}
void query2(int x,int y)
{
    int fx=findparent(x);
    int fy=findparent(y);
    if(fx==fy){
        printf("-1\n");
    }
    else{
        res=f[fx]&f[fy];
        printf("%d\n",res.count());
    }
}
int main()
{
    scanf("%d %d",&n,&m);
    cnt1=0,cnt2=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]){
            cnt1++;
        }
        else cnt2++;
    }
    int opt,x,y;
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d %d",&x,&y);
            addedge(x,y);
        }
        else if(opt==2){
            scanf("%d",&x);
            query1(x);
        }
        else if(opt==3){
            scanf("%d %d",&x,&y);
            query2(x,y);
        }
    }
    return 0;
}

F题:https://ac.nowcoder.com/acm/contest/368/F

有一棵n个节点的异或树,1号点为根,每个节点有一个权值w_i。每次询问给出u,x,询问子树u内,点的权值大于x的所有权值异或x的和。即

由于这是一棵异或树,所以,如果一个数出现了两次,那么这两个点的权值就消失了(点并没有消失,即树的形态没有发生变化,只是在计算时忽略这两个点的权值。)消失过程发生在一次询问时,如果子树内两个点的权值一样,那么这两个点的权值同时消失,直到无法再有点对消失后查询。

题解:权值线段树合并。官方题解:
首先按位处理,那么问题转化为求一个子树内,权值大于x的,1(或0)的个数。
可以对每棵子树建立一棵权值线段树,每个叶子节点维护两个值size和cnt[],表示这个权值出现的次数,以及这个权值二进制分解后,每一位上1的个数。权值线段树每个节点维护cnt[],表示子树内二进制下每一位上1的个数和,现在可以在O(log2V)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
#define M 120010
typedef long long ll;
 
int cnt;
int lp[M * 33], rp[M * 33], sz[M * 33], n, q, w[M], rot[M];//rot[i]:i号结点在权值线段树的根的编号,sz[i]:以根结点为i的子树内权值出现的次数,lp[i[,rp[i]:结点i的左右孩子的结点编号
vector<int> to[M];
vector<pair<int, int> > que[M];
ll ans[M], poww[111];
 
struct Node{
    int sum[17];
}node[M*33];
 
void pushup(int now)
{
    sz[now]=sz[lp[now]]+sz[rp[now]];
    for(int i=0;i<=16;i++) node[now].sum[i]=node[lp[now]].sum[i]+node[rp[now]].sum[i];
}
void insert(int l,int r,int &now,int val)//权值线段树叶子节点的插入操作
{
    if(now==0) now=++cnt;//分配一个新的结点编号
    if(l==r){//找到该权值的位置,进行插入操作(插入要维护的值)
        if(sz[now]==1){
            sz[now]=0;
            for(int i=0;i<=16;i++){
                node[now].sum[i]=0;
            }
        }
        else {
            sz[now]=1;
            for(int i=0;i<=16;i++){
                if(val&poww[i]) node[now].sum[i]=1;
                else node[now].sum[i]=0;
            }
        }
        return ;
    }
    int mid=(l+r)>>1;
    if(val<=mid) insert(l,mid,lp[now],val);
    else insert(mid+1,r,rp[now],val);
    pushup(now);
}
int merge(int l,int r,int last,int now)//合并操作
{
    if(!now||!last) return now+last;
    if(l==r){//叶子结点的合并
        sz[last]=(sz[now]^sz[last]);
        for(int i=0;i<=16;i++){
            node[last].sum[i]=(node[now].sum[i]+node[last].sum[i])&1;
        }
        return last;
    }
    int mid=(l+r)>>1;
    lp[last]=merge(l,mid,lp[last],lp[now]);//合并两颗树的左孩子
    rp[last]=merge(mid+1,r,rp[last],rp[now]);//合并右孩子
    pushup(last);
    return last;
}
ll query(int l,int r,int now,int x)
{
    if(sz[now]==0||now==0) return 0;
    if(r<=x) return 0;
    if(l>x){
        ll res=0;
        for(int i=0;i<=16;i++){
            int a=node[now].sum[i];int b=sz[now]-a;
            if(poww[i]&x) res+=poww[i]*b;
            else res+=poww[i]*a;
        }
        return res;
    }
    int mid=(l+r)>>1;
    return query(l,mid,lp[now],x)+query(mid+1,r,rp[now],x);
}
void dfs(int now,int fa)//以dfs序来合并每颗线段树
{
    for(int i=0;i<(int)to[now].size();i++){
        if(to[now][i]==fa) continue;
        dfs(to[now][i],now);
        rot[now]=merge(1,n,rot[now],rot[to[now][i]]);
    }
    for(int i=0;i<(int)que[now].size();i++){//每合并完一个就对该树进行查询
        ans[que[now][i].second]=query(1,n,rot[now],que[now][i].first);
    }
}
int main()
{
    scanf("%d %d",&n,&q);
    poww[0]=1;
    for(int i=1;i<=16;i++) poww[i]=poww[i-1]<<1;
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        to[u].push_back(v);
        to[v].push_back(u);
    }
    for(int i=1;i<=q;i++){
        int u,x;
        scanf("%d %d",&u,&x);
        que[u].push_back(make_pair(x,i));
    }
    cnt=0;
    for(int i=1;i<=n;i++){
        insert(1,n,rot[i],w[i]);//对每个权值都建立一颗权值线段树
    }
    dfs(1,1);
    for(int i=1;i<=q;i++){
        printf("%lld\n",ans[i]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值