2023“钉耙编程”中国大学生算法设计超级联赛(1)(已更新1012 1010 )
1012
题意:有一棵树,可以把任意一个点作为根节点,每次A,B两个人操作,B先手,选择除了根节点外的节点,减去以他为根节点的树,谁最后不能操作,统计A不能操作的次数,答案为cnt/n
思路:先把问题简化,成以1为根结点,判断时候胜利,既然每次都是操作子孙节点,那么考虑用异或和(xor),
对于根节点u,子节点为
如果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,两种操作,
操作一,给你一个区间和一个值,让区间所有点更新为
操作二,查询区间和
思路: 区间操作区间查询,考虑线段树,但是这个每次操作的是与的绝对值,所以一般的线段树不能完成操作,又因为题意说了,值一直增大,所以当出现一次之后,当前节点的值就会一直小于x,每次都是 x-a[i];由于每次正负都改变,所以要开一个数组lazy2维护当前节点,表示改变值的符号。
答案所给提解释,维护两个线段树,线段树二里面的节点,都是 的情况,第一个线段树反之,但是会在有限的操作次数中,全部到第二个线段树中,第二个线段树可以更好的维护区间操作,所以时间复杂度为。
代码:
点击查看代码
#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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步