0%

cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。

n<=500000

很有趣的一道题

dp的做法我就不多赘述了.主要讲讲怎么$O(klogn)$实现它


首先我们考虑一个特殊情形:不限制树的数量上限,所有权值都为正数

假设$n=3$,用$01$串表示种树的情况的话,无非就是这两种:

  1. $010$
  2. $101$

假如目前策略是$010$的话,总价值$ans$为$val[2]$,如果满足条件:

$val[1]+val[3]>val[2]$

我们就可以改用策略$101$,此时价值增加量$f_1=val[1]+val[3]-val[2]$,种下的树的数量增加$1$

假设$n$大一些,例如$n=5$时,从$01010$转为$10101$的情形也大致一样:种下的树数量增加$1$,价值增加量为$f_2=val[1]+val[5]-f_1,(f_1含义见上一行,即01010的增加量)$

如果$f_2>0$,那么这个新的策略是值得的

于是我们发现了它重要的规律:这极其类似于求二分图匹配的增广路算法

也就是:路径上的$0$变为$1$,$1$变为$0$,种的树数量增加$1$

不过有一个区别:本题中进行一次类似”增广”的操作,会产生一个价值$f$,这个$f$可以通过容斥原理求得.

那么原题题意就明了了:我们的任务是进行不多于$k$次类似”增广”的操作,使得所有$f$之和最大


那么怎么实现这个操作呢?双向链表.

我们用一个节点来表示一段交错种树的坑的区间,初始状态下一个节点对应一个坑.

这个节点里要保存这段区间进行一次”增广”后产生的价值.初始状态就是原先这个坑的价值

如果进行了一次增广,那么区间就会和它左/右两边的区间合并,例如$0010100$,如果我们对区间$[3:5]$进行一次”增广”,那么就变成了$0101010$,区间扩大为$[2:6]$.这时候表示$[2:2]$的节点和表示$[6:6]$的节点都被$[2:6]$覆盖了,因此我们新建一个节点表示$[2:6]$,而原先的三个节点都打上删除标记.新节点直接与节点$[1:1]$和节点$[7:7]$相连.

由于我们要使$k$次”增广”的价值之和最大,显然把所有节点丢进一个大根堆里,按价值$f$从大到小取出就好了.


代码如下.

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <queue>
#define LL long long
using namespace std;
struct qwq{
        //the node in the Linked-list
        //it represents a sub-sequence of the array of holes
        LL val;//the value that the sequence can provide if we make it expand
        qwq *left,*right;
        bool if_del;//if the node has been deleted
        qwq (LL al,qwq *be):
                val(al),left(be),right(NULL),if_del(0){}
        qwq (LL al):
                val(al),left(NULL),right(NULL),if_del(0){}
};
struct pwp{
        //pointer to the node on the Linked-list
        qwq *p;
        bool operator < (const pwp &be)const{
                return p->val < be.p->val;
        }
};
priority_queue<pwp> h;
int main(){
        LL n,k;
        cin >> n >> k;
        LL al;qwq *be;
        scanf("%lld",&al);
        be = new qwq(al,NULL);
        h.push((pwp){be});
        for (LL i = 2;i <= n;++ i){
                scanf("%lld",&al);
                be->right = new qwq(al,be);
                be = be->right;
                h.push((pwp){be});
        }
        LL ans = 0;
        for (LL i = 1;i <= k;++ i){
                //take the node which owns the max value
                while(h.size() && h.top().p->if_del)
                        h.pop();
                qwq *p = h.top().p;
                h.pop();
                //we needen't to exactly chose k holes 
                if (p->val <= 0)
                        break;
                //make the sequence expand
                ans += p->val;//add value to the answer
                qwq *new_ = new qwq(0 - p->val);//create a new node to represent the larger sequence
                //the new node 's value is (left->val)+(right->val)-(origin->val)
                //if the left/right node exist
                //just like Augmenting-Path algorithm in gragh theory
                if (p->left != NULL){
                        //delete the left node,because it's sequence will be contained by the sequence of the new node
                        p->left->if_del = 1;
                        new_->val += p->left->val;
                        new_->left = p->left->left;
                        if (new_->left != NULL)
                                new_->left->right = new_;
                }
                else
                        new_->right = NULL;
                if (p->right != NULL){
                        p->right->if_del = 1;
                        new_->val += p->right->val;
                        new_->right = p->right->right;
                        if(new_->right != NULL)
                                new_->right->left = new_;
                }
                else
                        new_->right = NULL;
                //push the new node to the heap
                h.push((pwp){new_});
        }
        cout << ans;
        return 0;
}

文结.

Luogu2575高手过招|题解

AKN玩游戏玩累了,于是他开始和同伴下棋了,玩的是跳棋!对手是wwx!这两位上古神遇在一起下棋,使得棋局变得玄幻莫测,高手过招,必有一赢,他们都将用最佳策略下棋,现在给你一个n*20的棋盘,以及棋盘上有若干个棋子,问谁赢?akn先手!

游戏规则是这样的:

对于一个棋子,能将它向右移动一格,如果右边有棋子,则向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛。

显然每行游戏状态相互独立,可以分解为子问题.

每个子问题规模$20$,考虑压缩状态.

问题在于如何求得子问题的$SG$函数

阶梯Nim

一个阶梯有n阶,每一阶上面都可以存放个数不限的硬币。现在我们用n元组(x1,x2,…,xn) 来代表第 i (1 ≤ i ≤
n)个硬币存放在第xi个阶梯上面。阶梯博弈中的一次移动可以把任意正整数个的硬币从某一阶移动到下面的一阶,也即是从第 j 阶移动到第j -
1阶。当硬币到达地面(第0阶)时,这个硬币就不可再移动了。双方轮流落子,直到有一方不能再落子。

阶梯Nim中我们遵循以下策略:

  • 若对手将奇数阶的硬币移到偶数阶,我们不作为
  • 若对手将偶数阶的硬币移到奇数阶,我们将相同的这些硬币移至下一级偶数阶

由于所有硬币最后都会到达第$0$阶,所以根据以上策略,移至偶数阶的硬币相当于被拿走了

也就是问题变为对奇数阶的Nim游戏


而这个问题可以归结为阶梯Nim:将两个空位之间的间隙视为一级阶梯即可

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <algorithm>
#include <set>
#define LL long long
using namespace std;
LL T,n,sg_mem[2000000];
const LL N = 20;
LL sg(LL status){
        LL ans = 0,cnt = 0,j = 0;
        while(status){
                if (status & 1){
                        ++ cnt;
                        status >>= 1;
                        continue;
                }
                if (j % 2)
                        ans ^= cnt;
                cnt = 0;
                ++ j;
                status >>= 1;
        }
        if (j % 2)
                ans ^= cnt;
        return ans;
}
int main (){
        LL al,n,m;
        memset(sg_mem,-1,sizeof(sg_mem));
        cin >> T;
        while(T--){
                scanf("%lld",&n);
                LL ans = 0;
                for (LL i = 1;i <= n;++ i){
                        LL s = 0;
                        scanf("%lld",&m);
                        for (LL i = 1;i <= m;++ i){
                                scanf("%lld",&al);
                                s |= (1 << (20 - al));
                        }
                        ans ^= sg(s);
                }
                if (ans)
                        printf("YES\n");
                else
                        printf("NO\n");
        }
        return 0;
}

文结.

博弈论

并不是NOIp常考的内容,不过还是稍微学了下.


基本概念

我们定义几个概念

  • N状态(next position)表示一个状态先手必胜
  • P状态(previous position)表示一个状态先手必败

根据博弈规则,我们容易发现几个规律

  • 一个状态不是P就是N
  • 游戏结束时的状态为P
  • N状态有一个后继状态为P,这样先手只要转移到这个状态就能使对手必败
  • P状态所有后继状态都是N,先手无论如何转移对手都必胜

接着我们定义一个特殊的函数SG函数

这个函数是一个游戏状态集非负整数集的映射

它的定义利用一个特殊运算$mex$.$mex(s)$表示不属于非负整数集s的最小非负整数

例如$mex({0,1,2,3})=4$,$mex({0,1,3,4})=2$

$SG$函数通过递归定义:

  • 若状态$x$是P状态,$SG(x)=0$
  • 若状态$x$是N状态,设$x$的所有后继状态为$y_1,y_2,y_3\dots y_n$,则$SG(x)=mex({SG(y_1),SG(y_2),SG(y_3)\dots SG(y_n)})$

那么,$SG$函数的意义何在?

Nim游戏

Nim游戏是指这样一个游戏

给一个有$n$颗石子的石堆,两人轮流取若干颗石子,最后取完所有石子的人获胜

这是一个极简单的一维Nim游戏

很明显,$n=0$时先手必败,否则先手必胜

接下来我们从上述几个概念的角度理解这个游戏.

  • 状态:一维Nim游戏的状态可以用一个非负整数表示,即石子数量
  • N和P:设状态为$x$,则$x=0$时为P状态,否则就是N状态
  • SG:设状态$y$是当前状态$x$的后继状态,显然$y$可以是任何小于$x$的非负整数.根据$SG$函数的定义,我们发现$SG(x)=x$

因此,笔者认为$SG$函数缘于Nim游戏

如何计算复杂状态的$SG$值呢?

$SG$函数的运算

给若$n$个有$A_i$颗石子的石堆,两人轮流取若干颗石子,最后取完所有石子的人获胜

这是一个多维的Nim游戏

  • 状态:游戏状态可以用一个非负整数集$s={x_1,x_2\dots x_n}$来表示.
  • N和P:$s={0,0\dots 0}$是一个P状态

其它状态的$SG$怎么求呢?

接下来定义Nim游戏中$SG$函数的运算规则:

Bouton’s Theorem

首先我们给出结论:

对于一个状态$s$,$SG(s)=x_1\ xor\ x_2\ xor… xor\ x_n$

接下来我们归纳证明这个结论的正确性:

根据游戏规则,设状态$s_1={x_1,x_2,\dots ,x_n}$有一个后继状态$s_2={x_1-m,x_2,\dots x_n}$

$SG(s_2)$

$=(x_1-m)xor\ x_2\ xor\dots xor\ x_n$

$=(x_1-m)xor\ x_2\ xor\dots xor\ x_n\ xor\ x_1\ xor\ x_1$

$=(x_1-m)xor\ SG(s_1)xor\ x_1$

1.$SG(s_1)=0,s_1$是P状态

因为$(x_1-m)\neq x_1$,所以$SG(s_2)\neq SG(s_1)\neq 0$,$s_2$是N状态

同理得$s_1$的其它所有后继状态都是N状态.满足博弈规则

2.$SG(s_1)\neq 0,s_1$ 是N状态

根据博弈规则,$s_1$一定有个后继状态$SG(s_i)=0$,也就是一定存在$x_j$满足:

$SG(s_i)=(x_j-m)xor\ SG(s_1)xor\ x_j=0$

即:

$x_j-m=SG(s_1)xor\ x_j$

因为一定存在$x_j$,其二进制最高位和$SG(s_1)$的二进制最高位相同(否则无法得到$SG(s_1)$最高位上的$1$)

所以$SG(s_1)xor\ x_j$的这个位置上一定为$0$,则一定$x_j>SG(s_1)xor\ x_j$,因此一定存在$m$满足上式

由此我们简单证明了Bouton's Theorem:在Nim游戏中,$SG(s)=x_1\ xor\ x_2\ xor… xor\ x_n$

推广至一般博弈游戏

$SG$函数就是一维Nim游戏

根据$SG$函数的定义$SG(x)=mex({SG(y_1),SG(y_2),\dots ,SG(y_n)})$,我们得到$SG$函数的性质:

若$SG(y)<SG(x)$,那么状态$x$一定可以转移到状态$y$

这和一维Nim游戏完全相同

如果对于更复杂的游戏,我们计算出每个状态的$SG$值,那么我们就把这个游戏变成了一个一维Nim

而N/P状态规律就是:$SG(x)=0$时状态$x$为P,否则状态为N

那么对于复杂游戏,$SG$函数又有什么运算规则呢?

Sprague-Grundy Therem

若复杂状态$s$可以看作若干个互不干涉的简单状态$x_1,x_2\dots x_n$的合成,那么:

$SG(s)=SG(x_1)xorSG(x_2)xor\dots xorSG(x_n)$

这实际上是对Bouton's Theorem的推广

  • 对于Nim游戏:$SG(s)=x_1\ xor\ x_2\ xor… xor\ x_n$
  • 对于一维Nim游戏:$SG(x)=x$

类比Bouton's Theorem,我们可以用同样的方式证明Sprague-Grundy Therem

综上所述

求解博弈论问题的一般步骤是

  1. 将复杂的博弈问题分解成一个个独立的简单博弈问题
  2. 通过定义递归求得简单状态的$SG$函数
  3. 通过Sprague-Grundy Therem将其合并成复杂状态的$SG$函数

$SG$函数源于Nim又高于Nim,它是利用Nim游戏的性质构造出来的函数,目的是把一般博弈问题都当成Nim求解

文结.

文章参考Estimator的博客

2018年国庆,或许是自己最后一次到qbxt上课了.距离NOIP2018已经不足一个月了.

9-30

早上六点五十分在学校门口集合 随即乘公交前往火车站.

我取了大家的身份证,往取票机去了.将同学们的火车票取出后准备取老师的票,哪知竟没有看见往北京的车程!

我急忙回去告诉老师,大家一时间慌了神.老师竟忘了买自己的票.

此时据发车已不到1个小时,莆田到北京的火车早已售空.所幸还是买到了莆田到福州的票,接着一路补票到了北京.

下午约4点,我们从北京南下了高铁–第一次见晚上的北京南.以往到北京总是乘车在北京西下车.

然后一路奔波到了华北电力大学国际交流中心.虽然外墙看起来略破损,不过内部装修却还略好过北农酒店.大家各自回房整顿行李后时间已接近九点.

整顿过后大家一起到北农路的街边吃了些烧烤和炸鸡,便回了酒店.曾焱睡得早,我大约12点半也睡下了.

iNOsSK.jpg

iNOxf0.jpg

10-1 ~ 10-6

没什么特殊的事情发生,和往常一样上课下课.只是国交相比北农,离早餐铺是远了.每天早上约7点起床,在路上花费的时间大约半个小时.

北农路的店面比起去年变了不少,以前挺喜欢的一家驴肉火烧已经关门了,隔壁新开的梅菜扣肉饼味道也不错.还要说有什么变化的话,大概就是晚上变得热闹了,去年被赶走的小摊都回来了,晚上放学后随时可以去吃点宵夜.

某天兴起买了个google cardboard玩玩,体验确实不错,并且把曾焱一起拉进了坑里hhh

上课两天算一个周期,每隔一个午就一场比赛.但是每天下午的比赛状态往往不是很好.前几次考试总是数学题,也很充分暴露了自己在数学方面的薄弱.大概比赛的试题还会单独写些题解.

平淡的日子过得很快.

iNXSpV.jpg

iNOBJx.jpg

iNO0F1.jpg

10-7

早上,我手机在夜里断了点以及曾焱闹钟的事故,导致两人8点才醒来.匆忙洗漱之后赶到了机房.

最后一天的课总是略水.和hzwer聊了会儿天,就要到11点半下课了.

hzwer好可爱啊啊啊

于是又到了旅游的时候.中午各自回去休息,打算下午去国家科技馆玩玩.

两点半,曾焱先起来叫醒了我,和许巍,刘丞宇和欧荣煌先骑上自行车往城里去了.路上各种状况不断,例如许巍的车驶出服务区啥的.约四点半我们到了国家科技馆,然而科技馆5点就要闭馆,所以并没有进去.

由于科技馆就在奥林匹克公园附近,我们决定去奥林匹克公园.和另一批人联系好在鸟巢下边碰面.这一天恰好碰上了中国新歌声的总决赛在鸟巢进行,门口有不少黄牛在卖票.等人的间隙间大家看了会儿hzwer的直播hhh

iNXJht.jpg

iNXGtI.jpg

iNX8AA.jpg

iNX17d.jpg

iNOMon.jpg

iNOmLQ.jpg

汇合之后我们在鸟巢附近逛了逛,也拍了些照,打算去吃饭了.路过水立方时,许巍有些饿了,路边买了烤肠吃.我和步骥商量着要不要进水立方.哪知一回头,其他人竟然都不见了.我们本打算劝服许巍和我们一起进水立方,他却执意要追上其他人吃饭(于是就丢了hhh

水立方当天正办大型展览<月光如水>,一个直径约4,5个人高的全还原月亮挂在水池上空,和池里倒影相映成趣.

iNOUeJ.jpg

iNOtL4.jpg

iNOKds.jpg

从水立方出门后,我们打算和其他人汇合一起吃饭,结果路上各种耽搁,到时已经过了一个小时.他们已经吃完准备离开,我和步骥便在旁边的川菜店吃了些晚饭.随后便准备回去了.

iNOdoR.jpg

iNOJQU.jpg

我们本该在龙泽站下车后乘公交回去.然而由于嫌等公交麻烦,我们便打算在另一处下车后骑车回去,哪知车站门口仅有的两辆车,一辆被多次报修,另一辆连二维码都被撕了下来.不得已只得不行回去.

由于步骥不太靠谱的导航,我们稍微走错了点路.后来找到路回去时,却发现穿过北农校园的路晚上都关上了大铁门.最后绕了不少路回去,一看google健康,已经完成了一周的运动目标.

晚上大家聚在一起开个了小会,发现每个人都有不同的境遇hhh.本来两批人最后竟分成了四批人.曾焱和刘丞宇在家乐福丢了寄存柜的凭证,手机没电的林奕鑫和没有手机的许巍险些被关在地铁站里,以及懒得等公交境遇和我们类似的邓谢喆..

晚上在床上玩cardborad,约3点半睡下了

10-8 ~ 10-9

早上10点四十左右醒来,洗漱收拾了行李.回来才发现落下了眼罩和一条裤子emmm

中午老师破费,大家聚餐吃了一顿.接着就赶往火车站了.在站里的麦当劳点了一杯拿铁雪冰,就上车了.我先睡了一会儿,起来吃了顿饭,闲聊了一会儿,就又上床玩手机了.第二天睡醒时已经快到家了.

iNXplT.jpg

下午整理了一些照片,晚上就回到学校了


眨眼间,据NOIP 2018已经不到一个月了.祝大家和自己武运隆昌.

文结.

本文所有图片均为笔者拍摄

趁着自己还能记得一些事情,把百团大战的感想写一写.


事实上,百团大战是第一次自己全权负责的活动.

由于去年百团情况并不是很理想,可以说这次自己是从头开始的一次探索.更何况,社团的香火延续堪称大事.

无疑,自己承担了相当大的压力.

尽管自己在全程中承担了大部分的工作,不过所幸的是,微笙这个没人社团里还是有许多人帮上了大忙.尤其要感谢陈峰宇同学,虽然有点傻乎乎的还有点中二,但是前期宣传和百团当天迎接新生的过程中实在是帮上了大忙.没有他的话我一个人完全应付不来.当然,雁琳,奕鑫等等其他人也提供了一定程度上的帮助,不一一赘述了.

按顺序讲讲百团全过程的准备工作吧


准备

要准备的事情实在是太多了,不过总结一下的话大概这么几点

  • 包括海报在内的事前宣传
  • 社联方面的工作协调(包括场地安排)
  • 百团当天的展示项目
  • 百团当天的纳新信息表

准备工作在百团当周的周三才开始进行,时间上略显紧促,虽然最后顺利的完成了各项工作,但总体上还略显粗糙

事前宣传

本来在军训期间就进班写了小黑板宣传,后来开学后又发了小传单,百团前一周又转发了几遍说说,纳新群和18年oi群都发了全员消息和公告,算是比较到位了.

海报做起来比较赶时间.我设计成了比较通用的样式,可以多次使用.

顺便还把logo给重置了,然后当成了新的会旗图案.花的时间不亏.

希望新一年会有美工完成这方面的工作..

img

社联方面的工作协调

主要是要填活动申请表和场地安排.

活动申请表

出现了重大事故!由于没写好申请桌椅物资,导致后来只能征用其他人的桌椅以及捡其它社团挑完剩下的..

场地安排

社联发的地图实在是充满魔幻主义气息..我会的地点看起来像是在泮池水上..

img

仔细观察后我发现我会的场地可能会被其它社团挡住..通过身为湄轩副社的张晨晖得知了整面实验楼侧墙居然只有湄轩一个社团!所以就和晨晖商量了一下搬到了那里.最后和社联主席报备了就完事.

(虽然百团当天这块地方最后是被微笙湄轩魔方社手工社四个社团共同瓜分了..不过场地也绰绰有余!!

不得不说协调后场地是在是明智的决定!身处漫研社和街舞社两个人流量磁铁中间,蹭到流量实在不少(虽然也有一点私心就是了hhh

百团当天的展示项目

经过思考后选中了四个项目

  • mordament
  • 压扁小鸟
  • 2048(原创)
  • 自动诗人(原创)

百团当天有人问是不是原创..总是难以回答..

希望新一年会有更多的原创作品!

其中压扁小鸟和2048反响最好.看来还是娱乐性比较重要hhhh

数独部的单独卷子难度上偏高了..尽管卷子发完了但是交卷的基本没有..

纳新信息表

问题设计包括能力调查和自由回答

能力调查中查出了许多大佬,自由回答则发现了许多有趣的小朋友hhh

个人对信息表设计还是比较满意的

不过个人信息部分留的空格太小,以至于看不清填表者的姓名和qq号..在统计阶段造成了不小的困扰

但是!!数量上实在太多了,竟然印了200张..导致了另一些本来不必要的麻烦..


当天

所谓计划赶不上变化..实在是体验的很明显..

由于桌椅申请手续有问题,导致后来只能征用其他人的桌椅以及捡其它社团挑完剩下的.

本来只是计划4张桌子足矣,但是看到对面漫研摆了整整一排!所以就让人又搬了一些,总共7张,一字排开还算挺长.

事实证明!桌子数量就是排面!尽管只有三台笔记本,但是桌子还是满满当当地围着人.所以说,人手充足的情况下,桌子数量就是排面!!

说道笔记本.由于室外没法接电线,所以四处借笔记本.由于准备开始的时间晚了,最后只借到了两台笔记本,算上自己三台.尽管电池很不行,但还是恰好度过了最繁忙的两个小时.

人手也是大问题.我会副会级和部长级分别是在役竞赛生,湄轩副社,摄协成员,滑板社成员..导致社团节前一天我发现竟然只有我一个人接待新生!!

所以当陈峰宇问我需不需要帮忙的时候,我简直要哭了..迎接新生的时候陈峰宇真的真的帮上了大忙,尤其是开头一个小时!!所幸后来雁琳也来了,算是轻松一些..

不过一开始雁琳不在的时候,许多新生问怎么没有小姐姐,甚至有女孩子没敢靠近..所以说有女孩子迎接新生还是很重要的!

还有海报.我直接把海报贴在了背后的宣传栏上面了..以至于存在感极低极低..早知道的话就做长条海报顺便申请海报展架了..


后续

当天回家后实在是累得不行,躺在床上根本不想动..

如果不是因为身为社团负责人,我根本不会挤进拥挤的人潮..更不会有一下午和上百人交谈的经历..当然,不得不说,是很珍贵的经历

结束活动的时候发现手上厚厚的一沓表格,实在感到非常满足.微笙计协作为只有一岁的没人社团,终于也迎来了第一批新人.

然后就是统计的过程了.通过表格信息发现了许多dalao(可供日后使唤hhh),但是由于前文提到的原因出现了信息上的不正确emmm

接着就要准备一下第一次全社大会了.在这之前还需要尽快确定新会服,课程安排,以及一些兜售的小零件之类…


越是疲倦,越不能歇.

祝自己和微笙计协都能够越来越好.

文结.