2025杭电暑期联赛第二场 (持续更新)

这场才做出来三个,艰难依旧坚持

09

苹果树
依然树剖
QQ_1753096978674

题意:
给定一棵树,q次查询
查询1:查找x,y路径上的最大值
查询2:使结点x邻接的结点+z


思路:
树剖+线段树板子容易维护查询1

重点在于查询2

与x的邻接结点:x的父亲结点(若有) + x的儿子结点(若有)

显然如果暴力维护查询2,最坏复杂度为O(nxn),无法接受

对于一颗树来说,每个结点可以有若干个儿子,但是只能有一个父节点

与其遍历某个结点所有的儿子结点,不如给这个结点打个tag

比如操作上图的1号(dfn序)结点,使11号,2号,16号结点+z

发现11号,16号为1号的轻儿子,而2号是1号的重儿子

同时我们知道轻儿子是另一条重链的链顶,而重儿子不是,11号和16号都是某重链的链顶

那么操作时不妨直接维护 父结点和重儿子结点,同时给当前结点打tag

没有+z的只剩下当前结点的轻儿子,即一些重链的链顶结点

在树剖+线段树维护x,y路径信息的算法过程中

每次深度深的点x往上跳到链顶的父亲结点f[top],此时不仅需要线段树维护dfn[top]~dfn[x]的最大值

还需要额外维护(链顶结点的权值+其父亲结点tag)的结果

int son[maxn],f[maxn],top[maxn];
int sz[maxn],dep[maxn];
int dfn[maxn],idx[maxn];
int tag[maxn];
int cnt;
vector<int>e[maxn];
int a[maxn];


void dfs1(int u,int fa){
    if(fa==-1)dep[u]=0;

    else dep[u]=dep[fa]+1;
    sz[u]=1;
    f[u]=fa;

    for(int v:e[u]){
        if(v==fa)continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if((!son[u])||sz[son[u]]<sz[v]){
            son[u]=v;
        }
    }
}
void dfs2(int u,int tp){
    top[u]=tp;dfn[u]=++cnt;idx[cnt]=u;
    if(son[u]==0)return;
    dfs2(son[u],tp);
    for(int v:e[u]){
        if(v==son[u]||v==f[u])continue;
        dfs2(v,v);
    }
}


#define ls p<<1
#define rs p<<1|1
struct node{
    int l,r;
    int mx;
}tr[4*maxn];
void pushup(int p){
    tr[p].mx=max(tr[ls].mx,tr[rs].mx);
}
void build(int p,int l,int r){
    tr[p].l=l;tr[p].r=r;
    if(l==r){
        tr[p].mx=a[idx[l]];
        return ;
    }
    int mid=l+r>>1;
    build(ls,l,mid);build(rs,mid+1,r);
    pushup(p);
}
void add(int p,int pos,int val){
    if(tr[p].l==tr[p].r&&tr[p].l==pos){
        tr[p].mx+=val;
        return;
    }
    int mid=tr[p].l+tr[p].r>>1;
    if(pos<=mid)add(ls,pos,val);
    else add(rs,pos,val);
    pushup(p);
}
int query(int p,int l,int r){
    if(l<=tr[p].l&&tr[p].r<=r){
        return tr[p].mx;
    }
    int res=0;
    int mid=tr[p].l+tr[p].r>>1;
    if(l<=mid)res=max(res,query(ls,l,r));
    if(r>mid)res=max(res,query(rs,l,r));
    return res;
}

//核心------------------------------------------------
int query_path(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);//此时x的链顶最深

        ans=max(ans,query(1,dfn[top[x]],dfn[x]));//重链部分最大值
        ans=max(ans,a[top[x]]+tag[f[top[x]]]);//链顶最大值
        x=f[top[x]];
    }
    if(dep[x]<dep[y])swap(x,y);//此时x,y位于相同的重链上 且y在x上
    ans=max(ans,query(1,dfn[y],dfn[x]));
    if(y==top[y])ans=max(ans,a[y]+tag[f[y]]);

    return ans;
}
//核心-------------------------------------------------

void solve(){
    int n,m;cin>>n>>m;

    {
    rep(i,1,n){
        son[i]=f[i]=top[i]=dep[i]=sz[i]=dfn[i]=idx[i]=tag[i]=0;
        e[i].clear();
    }
    cnt=0;
    }//初始化

    rep(i,1,n)cin>>a[i];
    rep(i,1,n-1){
        int u,v;cin>>u>>v;
        e[u].pb(v);e[v].pb(u);
    }
    dfs1(1,-1);
    dfs2(1,1);
    build(1,1,n);//重链剖分+线段树模板
    while(m--){
        int opt;cin>>opt;
        if(opt==1){
            int x,y;cin>>x>>y;
            cout<<query_path(x,y)<<endl;
        }else{
            int x,z;cin>>x>>z;

            //核心---------------------------------------
            if(f[x]!=-1){//父节点
                a[f[x]]+=z;
                add(1,dfn[f[x]],z);
            }   
            if(son[x]){//重儿子
                a[son[x]]+=z;
                add(1,dfn[son[x]],z);
            }
            tag[x]+=z;//打标记
            //核心---------------------------------------
        }
    }

}

02

题意:
给定x,y,求要使x转换到y的最少次数
1.让x变成一个popcount与它相同的数
2.让x变成一个最低位1与它相同的数
思路:
若x=y,则为0
若popcount(x)=popcount(y)||lowbit(x)=lowbit(y),则为1
否则,对于两个二进制110,001.要将110->001:110->011,011->001.要将001->110:001->010,010->110,则为2

(PS:发现数据超过int,需要使用__builtin_popcountll,而不能使用__builtin_popcount。不然就错!)

int lowbit(int x){return x&-x;}
void solve(){
    int n,x,y;cin>>n>>x>>y;
    if(x==y){
        cout<<0<<endl;return;
    }
    int a=__builtin_popcountll(x);
    int b=__builtin_popcountll(y);
    int c=lowbit(x);
    int d=lowbit(y);
    if(a==b||c==d){
        cout<<1<<endl;
    }else{
        cout<<2<<endl;
    }
}

06

题意:
给定两个排列,对于每一个i,求出使得它为第一时需要排除哪些人
思路:
一开始不知道如何去重
对于排列1:2 1 5 4 3,2:2 4 5 1 3
看编号为5的人,如果它要为两次第一需要排除2 1 4
即两个排列在它前面的人的集合的并集大小
想着如果n数据小一点可以用位运算按位或一下,再popcount求,然而n太大
不妨将第一场的人排名从高到低按序编个序号
如果第二场某个人发现第一场名次比它低的人这场比它高,那么这就是 第二场人的集合与第一场人的集合的并集的补集,用树状数组维护即可
记得清空

int tot[maxn];
int n;
int lowbit(int x){
    return x&-x;
}

void add(int p,int x){
    while(p<=n){
        tot[p]+=x;
        p+=lowbit(p);
    }
}
int query(int x){
    int res=0;
    while(x){
        res+=tot[x];
        x-=lowbit(x);
    }
    return res;
}


void solve(){
    cin>>n;
    rep(i,1,n){
        tot[i]=0;
    }
    vector<int>a(n+1);
    vector<int>b(n+1);
    rep(i,1,n)cin>>a[i];
    rep(i,1,n)cin>>b[i];
    vector<int>idx(n+1);
    vector<int>mp(n+1);

    rep(i,1,n){
        idx[a[i]]=i;
        mp[i]=a[i];
    }

    vector<int>s(n+1);
    vector<int>ans(n+1);
    rep(i,1,n)s[i]=idx[b[i]];
    rep(i,1,n){
        int byd=(i-1-query(s[i]));
        ans[mp[s[i]]]+=byd;
        add(s[i],1);
    }

    rep(i,1,n){
        ans[a[i]]+=(idx[a[i]]-1);
    }

    rep(i,1,n){
        cout<<ans[i]<<' ';
    }
    cout<<endl;
}

08

题意:
给定一个nxn矩阵,其中某一行或某一列为全1。但是不确定,因此有2n种可能,求把全部的1点掉的最小操作次数期望
思路:
期望=每一种可能的最小操作数/2n
显然从左下往右下点是最优的操作方法
如果碰到了1,说明这一行或这一列为全1.那么你有1/2的可能正好点到1,也有1/2的可能点到0,也就是操作次数比正好点到的多1
发现分子是个类似等差数列的东西

int cal(int n,int a1,int an){
    return n*(a1+an)/2;
}
void solve(){
    int n;cin>>n;
    int k=2*n;
    int num=k-n-1;
    double ans=n+k+cal(num,n+1,k-1)*2;
    ans=ans/(k*1.0);

    cout<<fixed<<setprecision(4)<<ans<<endl;
}

12

题意:
给定一个数组,要求选取一个序列使得异或值最大,且该序列两个元素不能相邻。输出异或值
思路:
(不保证完全正确 😄)
贪心线性基+搜索
线性基塞的数越多能表示的异或值越多
因此想要值可能大,就要塞更多的数
初始从下标1或2开始,可以隔1项或2项塞数
挺抽象的反正

struct LB{
int d[64];
LB(){memset(d,0,sizeof d);}
int f;
void insert(int x){
    for(int i=63;i>=0;i--){
        if(x&(1ll<<i)){
            if(!d[i]){
                d[i]=x;break;
            }else x^=d[i];
        }
    }
    f=1;
}
int check(int x){
    for(int i=63;i>=0;i--){
        if(x&(1ll<<i))x^=d[i];
    }
    return x==0;
}
int ask_max(){
    int ans=0;
    for(int i=63;i>=0;i--){
        if((ans^d[i])>ans)ans^=d[i];
    }
    return ans;
}
int ask_min(){
    if(f)return 0;
    for(int i=0;i<=62;i++){
        if(d[i])return d[i];
    }
}
};
int n;
int ans=0;
void dfs(int index,LB x,vector<int>&a){
    if(index>n){
        ans=max(ans,x.ask_max());
        return;
    }
    x.insert(a[index]);
    dfs(index+2,x,a);
    dfs(index+3,x,a);
}
void solve(){
    cin>>n;
    ans=0;
    vector<int>a(n+1);
    rep(i,1,n)cin>>a[i];
    LB st;
    dfs(1,st,a);
    dfs(2,st,a);
    cout<<ans<<endl;
}
posted @ 2025-07-21 19:54  Marinaco  阅读(49)  评论(0)    收藏  举报
//雪花飘落效果