JZOJ4759. 石子游戏

本文介绍了一种基于树状结构的Nim游戏算法,利用Splay树维护节点状态,实现对子树的有效查询和更新。适用于有增加操作的游戏场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定一棵n个节点的树和t个操作。
一开始每个结点有ai个石子。
操作有三种:
操作
对于1操作的询问,两人在以v为根节点的子树上玩Nim游戏。每次一人可以选择从除根节点外的一点取出不超过m个石子到它的父亲,判断先手是否必胜。

Data Constraint
n,t50000

题解

如果是在序列上做游戏,那么这个游戏就等价于每个结点石子数为ai%(m+1)的Nim游戏。
然后放到树上,就类似阶梯Nim游戏了。显然只有在到根距离为奇数的结点上做游戏才是有效的。
得到以上两条结论之后,现在问题就转化为询问子树内深度为奇数或者偶数的结点权值异或和。
因为有增加操作,所以考虑用Splay维护DFS序。
那么怎么找v的子树呢?我们记在DFS序上,v后面第一个深度小于等于v的结点为x。那么v的子树就是[DFN[v]+1,DFN[x]1]这一段。我们只要在Splay里维护子树内深度最小的结点就可以查找出x了。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 100000 + 10

int Node[2*N] , Next[2*N] , Head[N] , tot ;

int Son[N][2] , Pre[N] , Val[N] , Deep[N] ;
int Size[N] , OddXor[N] , AllXor[N] , MinDep[N] ;
int A[N] , Dep[N] , D[N] ;

int a[N] , dep[N] ;
int n , m , t ;
int Root , Cnt ;

bool Check( int x ) {
    return x != 0 ;
}

void link( int u , int v ) {
    Node[++tot] = v ;
    Next[tot] = Head[u] ;
    Head[u] = tot ;
}

void DFS( int x , int F ) {
    D[++D[0]] = x ;
    for (int p = Head[x] ; p ; p = Next[p] ) {
        if ( Node[p] == F ) continue ;
        dep[Node[p]] = dep[x] + 1 ;
        DFS( Node[p] , x ) ;
    }
}

void Update( int now ) {
    int ls = Son[now][0] ;
    int rs = Son[now][1] ;
    MinDep[now] = min( Deep[now] , min( MinDep[ls] , MinDep[rs] ) ) ;
    OddXor[now] = OddXor[ls] ^ OddXor[rs] ^ ( (Deep[now] & 1) ? Val[now] : 0 ) ;
    AllXor[now] = AllXor[ls] ^ AllXor[rs] ^ Val[now] ;
    Size[now] = Size[ls] + Size[rs] + 1 ;
}

int Build( int l , int r , int Fa ) {
    if ( l > r ) return 0 ;
    int mid = (l + r) / 2 ;
    int now = D[mid] ;
    Pre[now] = Fa ;
    Val[now] = A[mid] ;
    Deep[now] = Dep[mid] ;
    Son[now][0] = Build( l , mid - 1 , now ) ;
    Son[now][1] = Build( mid + 1 , r , now ) ;
    Update( now ) ;
    return now ;
}

void Rotate( int x ) {
    int F = Pre[x] , G = Pre[F] ;
    int Side = Son[F][1] == x ;
    Pre[x] = G , Pre[F] = x ;
    Son[F][Side] = Son[x][!Side] ;
    Pre[Son[x][!Side]] = F ;
    Son[x][!Side] = F ;
    Son[G][Son[G][1]==F] = x ;
    Update(F) ;
    Update(x) ;
}

void Splay( int x , int Goal ) {
    while ( Pre[x] != Goal ) {
        int y = Pre[x] , z = Pre[y] ;
        if ( z != Goal ) {
            if ( (Son[y][0] == x) ^ (Son[z][0] == y) ) Rotate(x) ;
            else Rotate(y) ;
        }
        Rotate(x) ;
    }
    if ( !Goal ) Root = x ;
}

int Find( int x ) {
    if ( MinDep[Son[x][0]] <= Deep[Root] ) return Find(Son[x][0]) ;
    if ( Deep[x] <= Deep[Root] ) return x ;
    return Find(Son[x][1]) ;
}

int Query( int now ) {
    Splay( now , 0 ) ;
    int Rson = Find(Son[Root][1]) ;
    Splay( Rson , Root ) ;
    now = Son[Rson][0] ;
    if ( Deep[Root] & 1 ) return Check( AllXor[now] ^ OddXor[now] ) ;
    else return Check( OddXor[now] ) ;
}

void Modify( int now , int v ) {
    Splay( now , 0 ) ;
    Val[now] = v ;
    Update(now) ;
}

void Insert( int x , int y , int v ) {
    Deep[y] = Deep[x] + 1 ;
    Val[y] = v ;
    Splay( x , 0 ) ;
    Pre[y] = x ;
    Son[y][1] = Son[x][1] ;
    Son[x][1] = y ;
    Pre[Son[y][1]] = y ;
    Update(y) ;
    Update(x) ;
}

int main() {
    scanf( "%d%d" , &n , &m ) ;
    for (int i = 1 ; i <= n ; i ++ ) scanf( "%d" , &a[i] ) ;
    for (int i = 1 ; i < n ; i ++ ) {
        int u , v ;
        scanf( "%d%d" , &u , &v ) ;
        link( u , v ) ;
        link( v , u ) ;
    }
    scanf( "%d" , &t ) ;
    dep[1] = 1 ;
    DFS( 1 , 0 ) ;
    D[++D[0]] = n + t + 1 ;
    A[n+t+1] = Dep[n+t+1] = 0 ;
    for (int i = 1 ; i <= D[0] ; i ++ ) {
        A[i] = a[D[i]] % (m + 1) ;
        Dep[i] = dep[D[i]] ;
    }
    memset( MinDep , 63 , sizeof(MinDep) ) ;
    Root = Build( 1 , n + 1 , 0 ) ;
    int Last = 0 ;
    for (int i = 1 ; i <= t ; i ++ ) {
        int op ;
        scanf( "%d" , &op ) ;
        if ( op == 1 ) {
            int v ;
            scanf( "%d" , &v ) ;
            v = v ^ Last ;
            if ( Query(v) ) printf( "Yes\n" ) , Last ++ ;
            else printf( "No\n" ) ;
        } else if ( op == 2 ) {
            int x , y ;
            scanf( "%d%d" , &x , &y ) ;
            x = x ^ Last ;
            y = y ^ Last ;
            Modify( x , y % (m + 1) ) ;
        } else {
            int u , v , x ;
            scanf( "%d%d%d" , &u , &v , &x ) ;
            u = u ^ Last ;
            v = v ^ Last ;
            x = x ^ Last ;
            Insert( u , v , x % (m + 1) ) ;
        }
    }
    return 0 ;
}

以上.

资源下载链接为: https://pan.quark.cn/s/d9ef5828b597 在本文中,我们将探讨如何通过 Vue.js 实现一个带有动画效果的“回到顶部”功能。Vue.js 是一款用于构建用户界面的流行 JavaScript 框架,其组件化和响应式设计让实现这种交互功能变得十分便捷。 首先,我们来分析 HTML 代码。在这个示例中,存在一个 ID 为 back-to-top 的 div 元素,其中包含两个 span 标签,分别显示“回到”和“顶部”文字。该 div 元素绑定了 Vue.js 的 @click 事件处理器 backToTop,用于处理点击事件,同时还绑定了 v-show 指令来控制按钮的显示与隐藏。v-cloak 指令的作用是在 Vue 实例渲染完成之前隐藏该元素,避免出现闪烁现象。 CSS 部分(backTop.css)主要负责样式设计。它首先清除了一些默认的边距和填充,对 html 和 body 进行了全屏布局,并设置了相对定位。.back-to-top 类则定义了“回到顶部”按钮的样式,包括其位置、圆角、阴影、填充以及悬停时背景颜色的变化。此外,与 v-cloak 相关的 CSS 确保在 Vue 实例加载过程中隐藏该元素。每个 .page 类代表一个页面,每个页面的高度设置为 400px,用于模拟多页面的滚动效果。 接下来是 JavaScript 部分(backTop.js)。在这里,我们创建了一个 Vue 实例。实例的 el 属性指定 Vue 将挂载到的 DOM 元素(#back-to-top)。data 对象中包含三个属性:backTopShow 用于控制按钮的显示状态;backTopAllow 用于防止用户快速连续点击;backSeconds 定义了回到顶部所需的时间;showPx 则规定了滚动多少像素后显示“回到顶部”按钮。 在 V
资源下载链接为: https://pan.quark.cn/s/9e7ef05254f8 以下是简化后的内容: 程序集变量 计数器:整数型 文本发送计次:整数型 子程序 __启动窗口_创建完毕 _手动发送数据_被单击 停止发送 发送预处理 判断端口是否启动成功,失败则提示并返回 根据组合框选择的进制类型,将编辑框内容转换后发送 发送失败则提示并返回 进制转换(被转换文本,被转换进制,转换的进制) 检查进制范围,错误则返回提示 规范参数,逐字符检查是否符合进制要求,不符合则返回错误提示 若进制相同直接返回原文本 否则进行进制转换并返回结果 _退出_被单击销毁 _组合框_端口号_列表项被选择 停止发送 设置端口号 _组合框_波特率_列表项被选择 停止发送 设置波特率 _组合框_数据位_列表项被选择 停止发送 设置数据位数 _组合框_校验_列表项被选择 停止发送 设置奇偶校验方案 _组合框_停止位_列表项被选择 停止发送 设置停止位数 发送预处理 停止发送 设置波特率、端口号、数据位数、奇偶校验方案、停止位数 根据奇偶校验方案设置校验标志 _选择框_DTR_被单击 根据选中状态设置信号操作 _选择框_RTS_被单击 根据选中状态设置信号操作 _选择框_Break_被单击 根据选中状态设置信号操作 _编辑框_发送周期_内容被改变 若时钟标志选中,设置时钟周期 _选择框_时钟标志_被单击 若选中,设置发送方式为时钟模式,启动发送并设置时钟周期 否则,停止发送,设置时钟周期为0 _组合框_发送方式_列表项被选择 根据选择设置时钟标志和时钟周期 _端口_发送数据_收到信号 _端口_接收数据_收到信号 _端口_接收数据_数据到达 根据接收数据的进制选择,将数据转换后显示在编辑框中 _时钟1_周期事件 根据发送方式和进制选择,周期性发送数据 打开并读入文件 打开文件,读取内容到编辑框 _打开
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值