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

1012
题意:有一棵树,可以把任意一个点作为根节点,每次A,B两个人操作,B先手,选择除了根节点外的节点,减去以他为根节点的树,谁最后不能操作,统计A不能操作的次数,答案为cnt/n
思路:先把问题简化,成以1为根结点,判断时候胜利,既然每次都是操作子孙节点,那么考虑用异或和(xor),
对于根节点u,子节点为\(v_1,v_2...v_l\)
\(SG(u)=SG(v_1)xorSG(v_2)xor...SG(v_l)\)
如果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\),让区间所有点更新为$|a_i-x| $
操作二,查询区间和

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

代码:

点击查看代码
#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;
}

posted @ 2023-07-18 19:42  xxj112  阅读(255)  评论(0)    收藏  举报