“陌上人如玉,公子世无双!”|

xxj112

园龄:2年10个月粉丝:11关注:18

2023“钉耙编程”中国大学生算法设计超级联赛(1)(已更新1012 1010 )

1012
题意:有一棵树,可以把任意一个点作为根节点,每次A,B两个人操作,B先手,选择除了根节点外的节点,减去以他为根节点的树,谁最后不能操作,统计A不能操作的次数,答案为cnt/n
思路:先把问题简化,成以1为根结点,判断时候胜利,既然每次都是操作子孙节点,那么考虑用异或和(xor),
对于根节点u,子节点为v1,v2...vl
SG(u)=SG(v1)xorSG(v2)xor...SG(vl)
如果SG(u)>0则这点先手能走向胜利,SG(u)==0,表示,无论先手怎么选后手都能选择相同的,则后手胜利,反之先手胜利。
经过第一遍dfs后,可以确定点1的SG(1)函数,
利用换根dp,再跑一遍dfs即可求得,其余点的SG函数,原理,两次异或等价于没有异或
代码:

点击查看代码
#include <bits/stdc++.h>

#define Max 200005

using namespace std;

const int mod=1e9+7;

struct Edge{
    int v,to;
}e[Max*2];

int T,n,sz,head[Max],f1[Max],f2[Max];
//f1表示以1为根节点,每个节点的子节点的异或值
//f2就是SG函数

inline int qpow(int x,int y){ //快速幂
    int ans=1;
    while(y){
        if(y&1)ans=1ll*ans*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ans;
}

inline void add(int u,int v){ //邻接表,建树
    e[++sz].v=v;e[sz].to=head[u];head[u]=sz;
}

inline void dfs1(int u,int fa){ //深搜1
    int now=0;
    for(int i=head[u];i;i=e[i].to){
        int v=e[i].v;
        if(v==fa)continue;
        dfs1(v,u);
    //    cout<<f1[v]<<" "<<u<<" "<<now<<"dsa\n";
        now^=(f1[v]+1);
        
    }
    f1[u]=now;
    return;
}

inline void dfs2(int u,int fa){ //深搜2
    for(int i=head[u];i;i=e[i].to){
        int v=e[i].v;
        if(v==fa)continue;
        f2[v]=f1[v]^((f2[u]^(f1[v]+1))+1); //换根
        dfs2(v,u);
    }
    return;
}

int main(){
	// freopen("data2.in","r",stdin);
	// freopen("data2.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>n;
        for(int i=1;i<n;i++){
            int u,v;
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        dfs1(1,1);//以1为根节点
        f2[1]=f1[1];
        dfs2(1,1); //换根dp
        int cnt=0;
        for(int i=1;i<=n;i++){ //统计点数
            if(f2[i])cnt++;
        }
      //  cout<<cnt<<'\n';
        cout<<1ll*cnt*qpow(n,mod-2)%mod<<'\n';
        for(int i=1;i<=n;i++)head[i]=0; //初始化
        sz=0;
    }
    return 0;
}

1010
题意: 有一个数组a,两种操作,
操作一,给你一个区间l,r和一个值x,让区间所有点更新为|aix|
操作二,查询区间和

思路: 区间操作区间查询,考虑线段树,但是这个每次操作的是与x的绝对值,所以一般的线段树不能完成操作,又因为题意说了,x值一直增大,所以当出现一次aix之后,当前节点的值就会一直小于x,每次都是 x-a[i];由于每次正负都改变,所以要开一个数组lazy2维护当前节点,表示改变值的符号。
答案所给提解释,维护两个线段树,线段树二里面的节点,都是aix 的情况,第一个线段树反之,但是会在有限的操作次数中,全部到第二个线段树中,第二个线段树可以更好的维护区间操作,所以时间复杂度为。O(n+mlogn)

代码:

点击查看代码
#include <bits/stdc++.h>

#define ll long long
#define Max 200005

using namespace std;

struct Tree{
    ll lazy1,lazy2,lazy3,cnt,minn,sum1,sum2;
}st[Max*4];
//sum1、sum2分别表示两颗树的区间值
//cnt是当前区间第一个线段树中的点
//minn最小值
//lazy3表示第一棵树的加法懒惰数组
//lazy1表示第二棵树的加法懒惰数组
//lazy2表示第二棵树懒惰数组的正负值
int T,n,m,a[Max];
inline Tree up(Tree ls,Tree rs){
    Tree ans;
    ans.lazy1=ans.lazy3=0;ans.lazy2=1;//表示正负用1和-1表示,而只有一的话符号不改变。
    ans.minn=min(ls.minn,rs.minn);
    ans.sum1=ls.sum1+rs.sum1;
    ans.sum2=ls.sum2+rs.sum2;
    ans.cnt=ls.cnt+rs.cnt;
    return ans;
}

inline void down(int node,int L,int R){
    int ls=node<<1,rs=node<<1|1;
    int mid=(L+R)>>1;

        //第一棵树
        st[ls].lazy3+=st[node].lazy3;
        st[rs].lazy3+=st[node].lazy3;
        st[ls].sum1-=st[node].lazy3*st[ls].cnt;
        st[rs].sum1-=st[node].lazy3*st[rs].cnt;
        st[ls].minn-=st[node].lazy3;
        st[rs].minn-=st[node].lazy3;


        //第二棵树
        st[ls].lazy1=st[node].lazy1+st[ls].lazy1*st[node].lazy2;
        st[rs].lazy1=st[node].lazy1+st[rs].lazy1*st[node].lazy2;
        st[ls].lazy2*=st[node].lazy2;
        st[rs].lazy2*=st[node].lazy2;
        st[ls].sum2=st[node].lazy1*(mid-L+1-st[ls].cnt)+st[ls].sum2*st[node].lazy2;
        st[rs].sum2=st[node].lazy1*(R-mid-st[rs].cnt)+st[rs].sum2*st[node].lazy2;

        //当前节点初始化
    st[node].lazy1=st[node].lazy3=0;
    st[node].lazy2=1;
    return;
}

inline void build(int node,int L,int R){//建树
    if(L==R){
        st[node].lazy1=st[node].lazy3=0;
        st[node].lazy2=1;
        st[node].minn=st[node].sum1=a[L];
        st[node].sum2=0;
        st[node].cnt=1;
        return;
    }
    int mid=(L+R)>>1;
    build(node<<1,L,mid);
    build(node<<1|1,mid+1,R);
    st[node]=up(st[node<<1],st[node<<1|1]);
    return;
}

inline void change(int node,int l,int r,int L,int R,int k){
    if(L>=l&&R<=r){
        if(st[node].cnt){ //第一个线段树里面有值
            if(L==R){//一个点  
                if(st[node].sum1<k){//放到第二个线段树中 
                    st[node].sum2=k-st[node].sum1;//相当于取绝对值
                    st[node].sum1=st[node].cnt=0;//清空当前节点第一棵树的维护量
                    st[node].minn=1e18; //更新节点最小值(最大化),排除影响。
                }else{//还在第一个线段树中
                    st[node].minn=st[node].sum1=st[node].sum1-k;//直接更新即可
                }

            }else{ //多个点
                if(st[node].minn<k){ //放到第二个线段树
                    //这个操作时间复杂度很高,只有到 L==R 或者  下面这个else才结束,但是由题意可知总体操作次数有限。
                    down(node,L,R); 
                    int mid=(L+R)>>1;
                    change(node<<1,l,r,L,mid,k);
                    change(node<<1|1,l,r,mid+1,R,k);
                    st[node]=up(st[node<<1],st[node<<1|1]);
                }else{//第一个线段树
                    st[node].lazy3+=k;
                    st[node].minn-=k; 
                    st[node].sum1-=1ll*k*st[node].cnt;//相当于当前区间 第一个线段树中的cnt个点都减去k
                    st[node].lazy1=k-st[node].lazy1;
                    st[node].lazy2*=-1; //这里
                    st[node].sum2=1ll*k*(R-L+1-st[node].cnt)-st[node].sum2;
                }
            }
        }else{ //都在第二个线段树中
            st[node].lazy1=k-st[node].lazy1;
            st[node].lazy2*=-1;
            st[node].sum2=1ll*k*(R-L+1)-st[node].sum2;
        }
//        cout<<L<<" "<<R<<" "<<st[node].sum1<<" "<<st[node].sum2<<endl;
        return;
    }
    down(node,L,R); //当前节点下方
    int mid=(L+R)>>1;
    if(l<=mid)change(node<<1,l,r,L,mid,k);
    if(r>mid)change(node<<1|1,l,r,mid+1,R,k);
    st[node]=up(st[node<<1],st[node<<1|1]);//回溯更新节点维护值
    return;
}

inline Tree query(int node,int l,int r,int L,int R){
    if(L>=l&&R<=r)return st[node];
    down(node,L,R);//下放当前节点(也可以称为当前区间)的懒惰数组
    int mid=(L+R)>>1;
    if(r<=mid)return query(node<<1,l,r,L,mid);
    if(l>mid)return query(node<<1|1,l,r,mid+1,R);
    return up(query(node<<1,l,r,L,mid),query(node<<1|1,l,r,mid+1,R));//回溯返回需要的值
}

int main(){
    // freopen("data3.in","r",stdin);
    // freopen("data3.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>n>>m;
        for(int i=1;i<=n;i++)cin>>a[i];
        build(1,1,n);
        for(int i=1;i<=m;i++){
            int opt;
            cin>>opt;
            if(opt==1){
                int l,r,x;
                cin>>l>>r>>x;
                change(1,l,r,1,n,x);
            }else{
                int l,r;
                cin>>l>>r;
                Tree ans=query(1,l,r,1,n);
                cout<<ans.sum1+ans.sum2<<'\n';
            }
        }
    }
    return 0;
}

本文作者:xxj112

本文链接:https://www.cnblogs.com/xxj112/p/17563963.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   xxj112  阅读(222)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 椿 沈以诚
椿 - 沈以诚
00:00 / 00:00
An audio error has occurred.

作词 : 沈以诚

作曲 : 沈以诚

恍惚间 浸透了回忆

漫漫的回忆 存在的回忆

只看见 遗忘的笑脸

纯洁的笑脸不在身边

想实现 从前的诺言

不轻的诺言 和你的诺言

却描绘 无期限的诗

由寓意的诗刻画的线

乔木落叶 告诉我时间变迁

你仍旧倚靠石头看岁岁年年

惊觉两鬓霜白长叹一声哀怨

也不过匆匆弹指间

不在意我存在你心里面

只记得当时没有那么远

一眼朝如青丝暮成雪

美梦惊你不在眼前

我想给你一颗我的眼

倒映着曾拾起的缘

若皆是一场镜花水月

也不想忘记你的颜

唯一心愿

恍惚间 浸透了回忆

恍惚间 浸透了回忆

漫漫的回忆 存在的回忆

只看见 遗忘的笑脸

纯洁的笑脸不在身边

想实现 从前的诺言

不轻的诺言 和你的诺言

却描绘 无期限的诗

由寓意的诗刻画的线

乔木落叶 告诉我时间变迁

你仍旧倚靠石头看岁岁年年

惊觉两鬓霜白长叹一声哀怨

也不过匆匆弹指间

不在意我存在你心里面

只记得当时没有那么远

一眼朝如青丝暮成雪

美梦惊你不在眼前

我想给你一颗我的眼

倒映着曾拾起的缘

若皆是一场镜花水月

也不想忘记你的颜

唯一心愿

点击右上角即可分享
微信分享提示