【JZOJ5097】【GDOI2017 day1】取石子游戏

30 篇文章 0 订阅
11 篇文章 0 订阅

Description

经过了与中山大学学生处软磨硬泡之后,凡喵、叶妹、yzx 和图图终于成为了舍友。如果你不想和题面软摩硬 泡的话,请直接跳到加粗的部分开始读。
这道题目来源于宿舍中发生的一件小事。为了简明扼要地把这道题叙述清楚,对这个故事的内容进行了极大 的简化。如果想看这个题目的完整版本,凡喵很愿意提供给大家。详见随题目下发的文件。
这天叶妹去改 (san) 题 (guo) 目 (sha) 了,图图和凡喵觉得无聊,于是他们在一起玩取石子游戏(NIM):
“哼!都怪你!也不哄哄人家 QAQ。人家都输了 2147483647 局了。qwq 人家超想哭的,捶你胸口,大坏蛋!!!”。
“别介样,你看,我来教你玩嘛。”
说着,图图掏出了他的宝贝——一本厚厚的博弈论书。
凡喵一把夺过了图图的书,翻了两页没看懂,生气地把书一摔:“哼!什么鬼!那么长人家才不要看”
其实凡喵一直不喜欢博弈论,尤其是那种“双方采用最优策略”这种奇怪的假设。要是对面是个智障,这游戏 不就没法玩了么?而且在现实生活中,这种情况还常常发生。
凡喵更喜欢的,还是象棋这种对弈游戏。尤其是他最近看些 deep reinforcement learning 的东西(一种机器学 习技术)之后,越来越觉得这种常规博弈没有什么意思。于是他建议,在象棋棋盘上玩一局取石子游戏。
图图汗颜并拒绝了他,然后继续讲解 NIM 的策略。“你看,这要这里异或一下,然后取 mex,就能知道一开 始取几个啦 ,是不是很强,很厉害呀。”
“取 max?我刚刚就是取的 max 啊!”
“笨猫!不是 max,是 mex 啦!”
“mex 是什么呀?”凡喵觉得自己太菜,哇的一声哭了出来。
“在这里,mex 是定义在集合上的函数,mex(S) 表示 S 这个集合中,最小的非负整数喔。”
“哦,我理解了,好神奇呀。”
现在,图图想让你和凡喵一起玩一局取石子游戏以检验凡喵是否真正理解。游戏的规则是这样的:有 n 堆石 子,每堆石子有 ai 个石头,你和凡喵轮流去石子,可以一次在一堆石子中一次取任意多个,或者在非空的几堆石 子中每堆同时取任意多个石子,凡喵先手,请问你能获胜么?
然而,凡喵懒,并不想和你玩。
图图无奈,只得说:“为了检验你是否真的理解了,现在来考考你 balabalabala……”
由于太菜没听懂,凡喵睡着了。现在他的脑子晕晕的,只记得一些模糊的词句在脑海中回旋。由于考他的人是 图图,所以这好像是个图论题。但是出什么题跟他叫什么名字有关系么?给你们出小学生数学题的人是小学生么? 真是的,这神一般的逻辑也是没谁了。但是凡喵不管什么逻辑不逻辑,他说啥就是啥。
图论是什么?这好像是计算机科学专业的一门选修课。然而凡喵并不是学计科的,所以他没有上这门课。唉, 为了不在室友面前太丢人,不爱看书的凡喵只能捧起买了没读的图论课本开始学习。
一个图 G 可以表示为一个二元组 G = (V, E),其中 V 是一个集合,成为“点集”,V 中的元素称为“点”, E 是另外一个集合,其中的元素是点的二元组。显然,E ⊆ V × V ,其中 V × V 代表 V 与 V 的笛卡尔积, 即 V × V = {(u, v) : u, v ∈ V }。E 被称为“边集”。如果 (u, v) ∈ E,则称 u 向 v 连了一条边。
显然,E 定义了一种集合 V 上的关系 R。如果这种关系满足对称性,换句话说,如果若 (u, v) ∈ E 则必然有 (v, u) ∈ E 的话,我们称图 G 为一个无向图。无向图以外的图称为有向图。V 中元素的个数称为点数。对于有向图,E 中元素的个数称为边数。对于无向图,边数定义为 E 中元素数的一半。
当然,我们考虑的集合 E 中不含有重复的元素,我们称这种性质为“无重边”。如果,∀v ∈ V,(v, v) /∈ E,我 们称图 G 中没有“自环”。一个无重边没自环的图称为简单图。以下我们说的所有图,均是简单图。
考虑一个有向图,向其边集中添加尽可能少的元素,使其构成一个无向图。称得到的无向图称为这个有向图的 基图。
考虑一个简单无向图 G0 = (V0, E0),如果 ∀v ∈ V0, ∃u ∈ V0, 使得(u, v) ∈ E0,则称 G0 为连通图。特别地,如 果删除 E0 中任何一对元素 (u, v)和(v, u) 会使 G0 变成一个非连通图的话,称 G0 为一棵树。
如果一个有向图的边数与它的基图相等,且它的基图为一棵树的话,称这个有向图为树形图。
对于一个树形图,如果除去一个节点 root 外,剩下的节点中有且仅有一个点有连向它的边,则称这个树形图 为一个有根树。root 称为这棵树的树根。每个节点称为连向它的节点的“儿子”,连向它的节点称为它的“父亲”。 特别地,根节点没有父亲。
那么如果删除一棵有根树中某个点 v 与它父亲之间的那条边 (v <>root),就会得到两个联通子图。其中包含 v 的那个子图称为 v 的子树。特别地,定义整个图为 root 的子树。
看到这,凡喵终于看不下去了。摔……毕竟,这些内容对曾经打过 NOI 的他一点用途都没有。
那么问题来了,挖掘机技术……
凡喵终于回忆起图图的问题:
“如果给你一棵有根树,树根为 1,并且树的每个结点上有一个权值……”。
联想力无比强大的凡喵突然惊醒:诶?有根树?点上有权值?难道是……二叉查找树?平衡树?动态树?作为 一个十分钟盲打 LCT 一遍过的无脑数据结构选手,他一下子激动起来。
这时候 yzx 推门走了进来。热爱学习的他看到凡喵在想题,立刻上来追问凡喵在想什么。凡喵告诉他,是一 个动态树上的取石子游戏,每个节点上有若干个石子,然后开始从某个子树……
yzx 觉得他在扯淡。于是他跑去问图图。才知道了题目本来的面目(如果让凡喵继续把脑洞开下去,可能要再 有两页纸才能把这题描述清楚)。一言以蔽之:
“现在我想知道每个点,除它所在子树以外的结点权值集合的 mex,怎么做呢?”
yzx 听罢,这不是个水题么?然后留下凡喵在风中凌乱
凡喵思考了片刻,居然不!会!做!他正打算再一次因为自己太菜而哇的一声哭出来的时候,突然想起来今 年二月份的时候,他曾经和他的好战友小 k 一同破译过一段密码,那段密码利用了整体二分套可持久化的平衡树。 他意识到,这两个问题的解决方法有异曲同工之妙——暴力出奇迹,不走寻常路!。
“这个很简单呀,只要给叶妹打个电话就知道了。”然后凡喵就给叶妹打了电话,把图图的题目完整地讲了一遍
叶妹接到电话之后,听了凡喵描述的题目,说了一句“容我三思”,接下来一秒,叶妹又说了一句“看我的厉 害!”于是乎,叶妹只用了两秒钟便搞定了这道题。
现在凡喵想要考考你,如何解决这道叶妹花了两秒就想出来的题呢?

Data Constraint

数据范围
对于 20% 的数据:N ≤ 500, T ≤ 20 另外
50% 的数据:N ≤ 100000, T ≤ 5 最后
30% 的数据:N ≤ 1000000, T ≤ 1

Solution

这道题不难,难就难在题目理解大意……
题目是给你一棵树,问你当前除以i为根的子树外的其他点中未出现的第一个非负整数是什么。
我们可以考虑一个值x会成为某个点的答案,当且仅当该点在根到所有值为x的点的lca的路径上。那么我们只要把值相同的点求一下lca,把值从小到大暴力赋值,遇到赋值的就退出就好。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+5;
int a[maxn],first[maxn*2],last[maxn*2],next[maxn*2],deep[maxn],f[maxn][20],b[maxn],d[maxn],v[maxn];
int n,m,i,t,j,k,l,x,y,z,te,num,ln;
char ch;
int dg(){
    ch=getchar();int x=0;
    while (ch>=48 && ch<=57) x=x*10+ch-48,ch=getchar();
    return x;   
}
void lian(int x,int y){
    last[++num]=y;next[num]=first[x];first[x]=num;
}
void bfs(){
    int i=0,j=1,x,t;v[1]=deep[1]=1;
    while (i<j){
        x=v[++i];
        for (t=first[x];t;t=next[t])
            f[last[t]][0]=x,v[++j]=last[t],deep[v[j]]=deep[x]+1;
    }
}
void write(int x){
    if (x>=10) write(x/10);
    ch=x%10+48;putchar(ch);
}
int lca(int x,int y){
    int i,j,k;
    if (deep[x]<deep[y]) swap(x,y);
    for (k=ln;k>=0;k--)
        if (deep[f[x][k]]>=deep[y]) x=f[x][k];
    if (x==y) return x;
    for (k=ln;k>=0;k--)
        if (f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k];
    return f[x][0];
}
int main(){
    freopen("game.in","r",stdin);freopen("game.out","w",stdout);
    scanf("%d",&te);
    while (te){te--;
        scanf("%d%d\n",&n,&m);num=0;
        memset(first,0,sizeof(first));
        for (i=1;i<=n;i++)a[i]=dg();
        for (i=1;i<n;i++){
            scanf("\n");
            x=dg(),y=dg(),lian(x,y);
        }
        num=0;bfs();ln=log(n)/log(2);
        for (j=1;j<=ln;j++)
            for (i=1;i<=n;i++)
                f[i][j]=f[f[i][j-1]][j-1];
        memset(b,0,sizeof(b));memset(d,255,sizeof(d));
        for (i=1;i<=n;i++){
            if (!b[a[i]])b[a[i]]=i;
            else b[a[i]]=lca(b[a[i]],i);
        }
        for (i=0;i<=n;i++){
            if (!b[i]){
                for (j=1;j<=n;j++)
                    if (d[j]<0)d[j]=i;
                break;
            }
            x=b[i];
            while (x && d[x]<0) d[x]=i,x=f[x][0];
        }
        for (i=1;i<=n;i++)
            write(d[i]),ch=' ',putchar(ch);
        printf("\n");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值