【LGOJ5652】基础博弈论练习题

你以为这是道博弈论题目,其实……的确是啊

把考场上的思路完整的说一下

首先考虑倒推,如果最后一格的数是奇数,会怎么样?

以样例为例

显然,如果我们走到了最后一格,就只能在上面反复横跳,直到这一列的格子完全消失

很容易发现,这样的情况,最后一列就是一个必胜点(这里必胜点定义为先走到这里一定获胜)

那么,在这一列的前面m列之内,所有列都是必败点(因为如果走到这里,下一步对手一定可以走到必胜点)

用红色表示必胜点,蓝色表示必败点

接着继续考虑,如果要尽量避免走到蓝色的列(必败点),最后两人一定会在第二列上反复横跳,直到这一列消失

是不是很熟悉?对!这是和最开始情况一样的!

但这里,最后一格是偶数,显然它是必败点(先手过去一定会输)

那它前面那一列是什么点呢?因为是偶数,所以继承了前面的情况,也是必败点

到这里,我们已经可以得出一个结论了:

倒推,如果最后一列是奇数,那么这一列是必胜点,它前面的m列都必败
如果最后一列是偶数,那么这一列是必败点,所有玩家都会尽量避免走到这一列来

所以我们可以考虑连边!顺序枚举,对于每一列,把它前面的第一个必胜点向他连边

这样的话,连好后的图是一个以0为根的多叉树形结构!

对于每次的询问l和r,如果l是r的祖先,那么先手必胜,否则后手必胜!

那么现在的问题来了,给出一棵固定形态的树,怎么\(\Theta(1)\)地判断u是否是v的祖先呢?

dfs序!

在dfs的过程中统计一个点第一次被访问到的时间戳,作为其dfn

如果\(dfn[u]\leq dfn[v] \leq dfn[u]+size[u]-1\)的话,u就是v的祖先!

那么这题就这样解决啦!

具体的看代码吧

#include<bits/stdc++.h>
#define mod (1LL<<32)
#define ll long long
#define int long long
#define N 20000005
using namespace std;

int n,m,q,type,l[N],r[N];
int L,a[N],fa[N];//fa->这个点前面的第一个必胜转移点
ll ans=0;

int A,B,C,P;
inline int rnd(){return A=(A*B+C)%P;}

struct Edge
{
    int next,to;
}edge[N];
int cnt=0,head[N];

inline void add_edge(int from,int to)
{
    edge[++cnt].next=head[from];
    edge[cnt].to=to;
    head[from]=cnt;
}

template<class T>inline void read(T &res)
{
    char c;T flag=1;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}

int siz[N],dfn[N],tms;
void dfs(int u,int fa)
{
//  cout<<"dfs: "<<u<<" "<<fa<<endl;
    siz[u]=1;
    if(!dfn[u]) dfn[u]=++tms;
    for(register int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs(v,u);
        siz[u]+=siz[v];
    }
}

signed main()
{
    read(n);read(m);read(q);read(type);//数的个数,区间长度,询问,是否压缩
    for(register int i=1;i<=n;++i) read(a[i]);
    if(!type)
    {
        for(register int i=1;i<=q;++i)
            read(l[i]),read(r[i]);
    }
    else
    {
        read(A);read(B);read(C);read(P);
        for(register int i=1;i<=q;++i)
        {
            l[i]=rnd()%n+1;
            r[i]=rnd()%n+1;
            if(l[i]>r[i]) swap(l[i],r[i]);
        }
    }

    for(register int i=1;i<=n;++i)
    {
        L=max(0LL,i-m-1);
        if(a[i]&1)//奇数是必胜点
        {
            if(a[L]&1) fa[i]=L,add_edge(L,i);
            else fa[i]=fa[L],add_edge(fa[L],i);
        }
        else//偶数,先手必败
        {
            if(a[i-1]&1) fa[i]=i-1,add_edge(i-1,i);
            else fa[i]=fa[i-1],add_edge(fa[i-1],i);
        }
    }
    //处理出每个点倒序第一个必胜转移点 

    dfs(0,0);//dfs序
//  for(register int i=1;i<=n;++i) cout<<siz[i]<<endl;
    for(register int i=1;i<=q;++i)
    {
        int u=l[i],v=r[i];
        if(u==v)
        {
            if(a[u]&1) continue;
            else ans=(ans+i*i)%mod;
            continue;
        }
//      cout<<dfn[u]<<" "<<dfn[v]<<" "<<dfn[u]+siz[u]-1<<endl;
        if(dfn[u]<=dfn[v]&&dfn[v]<=dfn[u]+siz[u]-1) continue;//先手必胜,没有贡献
        ans=(ans+i*i)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

/*
4 2 1 0
2 2 0 0
1 4
*/

原文地址:https://www.cnblogs.com/tqr06/p/11854590.html

时间: 2024-10-08 14:44:20

【LGOJ5652】基础博弈论练习题的相关文章

Java基础——面向对象练习题

1.建立一个图形接口,声明一个面积函数.计算圆形和矩形的面积.注:体现面向对象的特征,对象值进行判断,用异常处理.不合法的数值需要出现"这个数值是非法的提示",不再进行运算. 2.在一个类中编写一个方法,这个方法搜索一个字符数组是否存在某个字符.如果存在则返回这个字符在数组中第一次出现的位置,否则返回-1.需要搜索的字符数组和字符都一参数的形式传递给该方法.如果传入的数组为NULL,则应该抛出IllegalArgumentException异常. 3.补充compare函数代码. 4.

6、50道JAVA基础编程练习题跟答案

1 50道JAVA基础编程练习题 2 [程序1] 3 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 4 程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21.... 5 public class Prog1{ 6 public static void main(String[] args){ 7 int n = 10; 8 System.out.println("第"+n+

【UOJ#228】基础数据结构练习题 线段树

#228. 基础数据结构练习题 题目链接:http://uoj.ac/problem/228 Solution 这题由于有区间+操作,所以和花神还是不一样的. 花神那道题,我们可以考虑每个数最多开根几次就会成1,而这个必须利用开根的性质 我们维护区间最大.最小.和.区间加操作可以直接做. 区间开方操作需要特殊考虑. 首先对于一个区间,如果这个区间的所有数取$x=\left \lfloor \sqrt{x} \right \rfloor$值一样,那么就可以直接区间覆盖. 分析上述过程,一个区间可以

JAVA基础编程练习题

50道JAVA基础编程练习题 [程序1] 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子对数为多少? 程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21.... public class Prog1{ public static void main(String[] args){ int n = 10; System.out.println("第"+n+"个月兔子总数为&qu

50道JAVA基础编程练习题

50道JAVA基础编程练习题[程序1]题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?程序分析: 兔子的规律为数列1,1,2,3,5,8,13,21....public class Prog1{public static void main(String[] args){ int n = 10; System.out.println("第"+n+"个月兔子总数为"+f

Linux运维常见基础面试练习题(1)

Linux运维常见基础面试练习题(1) 1 创建目录/data/oldboy,并且在该目录下创建文件oldboy.txt,然后在文件oldbot.txt里写入内容"inet addr:10.0.0.8 Bcast:10.0.0.225 Mask:255.255.255.0"不包含引号 方法一 [[email protected] /]# mkdir /data/oldboy -p [[email protected] /]# cd /data/oldboy/ [[email prote

Linux运维常见基础面试练习题(2)

Linux运维常见基础面试练习题(2) 11 如何查看占用端口8080的进程. 方法一 [[email protected] data]# ps -ef|grep 8080 root       3886   3804  0 17:11 pts/1    00:00:00 grep 8080 [[email protected] data]# 12 Linux所有服务的启动脚本都存放在(/etc/rc.d/init.d )目录中. A /etc/rc.d/init.d  B /etc/init.

Linux运维常见基础面试练习题(4)- 提升

Linux运维常见基础面试练习题(4)- 提升 1 (ZZ)命令是在vi编辑器中执行存盘退出 A :q    B ZZ    C :q!   D :WQ 2 用虚拟机安装了一台Linux系统,突然想克隆一台服务器,克隆后发现无法上网,如何解决? 1 删除linux网卡 eth0的uuid  Mac地址 2 清空 /etc/udev/rules.d/70-persistent-net.rules 3 reboot 3 常用网络检测命令及参数列举说明 1  ping 2  telnet 3  net

Linux运维常见基础面试练习题(3)

Linux运维常见基础面试练习题(3) 38 简述raid0.1.5的特点和优点. raid  0  需要硬盘 1块以上  容量n块数量之和  性能最高  冗余没有 raid  1  必须两块硬盘  容量是%50   性能 读不变 写较低  冗余 %100 raid  5  最少 4块  容量n块数量之和  性能 读写较低    冗余 %100 39 简要介绍:常用磁盘阵列方案及优缺点 raid  0  需要硬盘 1块以上  容量n块数量之和  性能最高  冗余没有 raid  1  必须两块硬