IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 小游戏叕AK了! -> 正文阅读

[游戏开发]小游戏叕AK了!

题目

题目背景

小游戏:“吾之 A K AK AK,已至大限,若再 A K AK AK,一字不足以概也;校长宝座,彼可取而代之。”

没事,我不担心字数。下一次就写成《小游戏叕又AK了!》

题目描述

一山难容二虎!除非一公一母,除非是校长和校监。

正好校长看到这样一个位运算:与非。记为 n a n d \mathrm{nand} nand 。运算规则是
a nand ? b = not ? ( a and ? b ) a\operatorname{nand}b=\operatorname{not}(a\operatorname{and}b) anandb=not(aandb)
其中 n o t \rm not not 是按位取反,而 a n d \mathrm{and} and 是按位与。它就很好的描述了 D D ( X Y X ) \sf DD(XYX) DD(XYX) O U Y E \sf OUYE OUYE 的处境:二人都在场,大家就不知道该受到谁的辖制;其一在场,大家都不敢大声说话;二者都不在,大家都害怕,校监在把什么报告给校长!

于是校长很喜欢它。校长写下了一个 0 / 1 0/1 0/1 串,表示每个时刻他是否在别人家恬静地坐着,在沉默无声中增进同事的感情。他打算对于一个区间内的每个前缀求出 n a n d \rm nand nand 和,然后求它们的异或值。即,记 principal ? ( l , r ) = a l nand ? a l + 1 nand ? ? nand ? a r \operatorname{principal}(l,r)=a_l\operatorname{nand}a_{l+1}\operatorname{nand}\cdots\operatorname{nand}a_r principal(l,r)=al?nandal+1?nand?nandar?(从左往右计算),那么要求
? i = L R principal ? ( L , i ) \bigoplus_{i=L}^{R}\operatorname{principal}(L,i) i=L?R?principal(L,i)
这里当然把 nand ? \operatorname{nand} nand 被定义为了 { 0 , 1 } \{0,1\} {0,1} 上的。也就是说,可以把位运算看成逻辑运算。

校长要利用这些数据,决定接下来他要不要去同事家里坐两个钟头。你能帮助思维能力退化到了前所未有的境地的校长,让他不被这种小问题击垮吗?

注意校长会发出两种指令:让你说出上面的问题的答案,和在当前的 a a a 序列后面附加一个数。

数据范围与提示

校长只会问 q = 1 0 5 q=10^5 q=105 个问题,但是他有 n = 4 × 1 0 6 n=4\times 10^6 n=4×106 次上门。

强制在线,上一次询问的结果会影响接下来的 a a a 序列插入,和下一次询问的区间。

T i p s ?? ( n o t ?? a v a i l a b l e ?? i n ?? y o u r ?? p r e f e r e d ?? l a n g u a g e : s i m p l i f i e d ?? C h i n e s e ) \rm Tips\;(not\;available\;in\;your\;prefered\;language:simplified\;Chinese) Tips(notavailableinyourpreferedlanguage:simplifiedChinese)

  • F r e n c h \rm French French Quelqu’un?a?vraiment?traduit?cette?phrase?inutile? \text{Quelqu'un a vraiment traduit cette phrase inutile?} Quelqu’un?a?vraiment?traduit?cette?phrase?inutile?
  • G e r m a n \rm German German Und?nach?dem?ersten?Fehlschlag?erwarten?Sie?immer?noch?etwas?Nettes? \text{Und nach dem ersten Fehlschlag erwarten Sie immer noch etwas Nettes?} Und?nach?dem?ersten?Fehlschlag?erwarten?Sie?immer?noch?etwas?Nettes?
  • J a p a n e s e \rm Japanese Japanese これは本物です:オンラインクエリ、“オフライン”の変更 \text{これは本物です:オンラインクエリ、“オフライン”の変更} これは本物です:オンラインクエリ、オフラインの変更

思路

我的思路

我的思路总是很奇怪。为什么奇葩的事老是发生在你身上啊?是因为你是一个奇葩的人吗?

注意到 n a n d \rm nand nand 操作是只有 1 , 1 1,1 1,1 会得到 0 0 0,其余都得到 1 1 1 的。假设 n a n d ( l , r ? 1 ) = 1 \mathrm {nand}(l,r-1)=1 nand(l,r?1)=1,那么下一位如果是 0 0 0,就仍然保持 n a n d ( l , r ) = 1 \mathrm{nand}(l,r)=1 nand(l,r)=1,否则变成 0 0 0,这必然使得 n a n d ( l , r + 1 ) = 1 \mathrm{nand}(l,r+1)=1 nand(l,r+1)=1,于是又进入这种形式。

那么,用 f ( i ) f(i) f(i) 表示前面得到一个 1 1 1 的时候,一路往后,所有 n a n d \rm nand nand 结果的异或和,则有 f ( i ) = { f ( i + 1 ) ⊕ 1 ( t h i s = 0 ) f ( i + 2 ) ⊕ 1 ( t h i s = 1 ) f(i)=\begin{cases}f(i+1)\oplus 1&(this=0)\\f(i+2)\oplus 1&(this=1)\end{cases} f(i)={f(i+1)1f(i+2)1?(this=0)(this=1)?

那么,每个 0 0 0 i + 1 i+1 i+1 连边,每个 1 1 1 i + 2 i+2 i+2 连边,我们将得到一棵树。问题转化为了树上路径长度!

注意最后一步可能是走到 r ? 1 r-1 r?1,此时恰好 t h i s = 1 this=1 this=1,就会越过 r r r 。这种情况很容易判断,只需要看看 r ? 1 r-1 r?1 结尾的最长全 1 1 1 子串是奇数还是偶数长度。因为 0 , 1 0,1 0,1 出现时,不可能一步跨过二者,就一定会走到那个 1 1 1 上面去;剩下的全是 1 1 1,就直接数奇偶性。

如何求树上路径长度?注意这里的路径一定是一条链(祖先和子孙的关系),所以我们只需要维护每个点的深度。分几种情况讨论:

  • 如果结尾是 0 , t h i s 0,this 0,this,那么所有点都必然走到 t h i s this this,直接打全局 d e p + 1 dep+1 dep+1 的标记;
  • 如果结尾是 0 , 1 , t h i s 0,1,this 0,1,this,所有点都必然走不到 t h i s this this,因为它没有入边,所以不作任何修改;
  • 如果结尾是 1 , 1 , t h i s 1,1,this 1,1,this,似乎有一些点是能走到 t h i s this this 的呢……

着重讨论最后这种情况。仔细想想会发现,这两个 “子树” 其中之一是一条链!

不信,你就把第一个 0 0 0 找到。那么就是 0 , 1 , [ 1 ] , … , 1 , t h i s 0,1,[1],\dots,1,this 0,1,[1],,1,this 。第二个 [ 1 ] [1] [1] 是没有入度的!并且往后一直是 1 1 1,就是下标奇偶性固定的一个区间,接成一条链!

那么我们可以用两个树状数组(代表下标为奇或偶),维护区间加。

时间复杂度 O [ ( n + q ) log ? n ] \mathcal O[(n+q)\log n] O[(n+q)logn],好像比较危险?计算一下常数。每次查询需要 2 2 2 B I T \tt BIT BIT 操作,而每个插入需要最多 2 2 2 B I T \tt BIT BIT 操作。同时 B I T \tt BIT BIT 操作每次是约 log ? n 2 \frac{\log n}{2} 2logn? 的(因为操作次数是 x x x 二进制下 1 1 1 的数量,每一位只提供 1 2 \frac{1}{2} 21? 的期望),所以常数约为 1 1 1,挺快的。

有什么优化点吗?比如说, B I T \tt BIT BIT 修改操作是单调的(从左到右)。然而也并不会。

正解思路

只要把上面的做法扩展一点, f ( i , ? a ? ) f(i,\langle a\rangle) f(i,?a?) 表示第一个数字是 i i i 时,与 ? a ? \langle a\rangle ?a? n a n d \rm nand nand 之后得到什么。这样显然就让线段树区间可以合并了嘛!再维护一个 g ( i , ? a ? ) g(i,\langle a\rangle) g(i,?a?) 表示 n a n d \rm nand nand 结果的异或和,搞定。(这玩意儿可以推广到任意位运算,甚至是任意简单函数。)

如果每次暴力修改线段树,还是 O ( n log ? n ) \mathcal O(n\log n) O(nlogn) 的。可是线段树如果是 b u i l d build build 就是 O ( n ) \mathcal O(n) O(n) 了!没错,进行一次修改的时候,如果某个节点的区间还没填满,就不要继续往上更新了,反正查询也不会用到。这样就是 O ( n ) \mathcal O(n) O(n) 次线段树修改了!

时间复杂度 O ( n + q log ? n ) \mathcal O(n+q\log n) O(n+qlogn) 。那就是 “在线询问,离线修改” 的含义。话说有人看提示吗。

代码

官方题解的做法可能比较简单,我干脆贴我的代码好了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int_ x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 4000005;
const int MaxM = MaxN>>1;
bool c[2][MaxM]; // even/odd BIT
int two[MaxN], one[MaxN];
bool dep[MaxN], copi[MaxN];

void addBit(int d,int id){
	id = (id+1)>>1;
	for(int i=id; i<MaxM; i+=(i&-i))
		c[d][i] ^= 1; // only 1
}
bool askBit(int d,int id){
	bool res = 0; id = (id+1)>>1;
	for(int i=id; i; i&=(i-1))
		res ^= c[d][i];
	return res;
}

int main(){
	bool all = 0, lst = 0;
	int T = readint(), n = 0;
	for(int opt,x; T; --T){
		opt = readint(), x = readint();
		/* insert */ ;
		if(opt == 1){
			copi[++ n] = (x ^= lst);
			/* update one */ ;
			if(!x) one[n] = 0;
			else one[n] = one[n-1]+1;
			/* four types */ ;
			if(!one[n-1]) all ^= 1;
			else if(one[n-1] == 1)
				two[n] = 1; // isolated
			else{ // worst type
				if(two[n-2]){ // chain
					two[n] = two[n-2]+1;
					addBit(n&1,n-2*(two[n]-1));
				}
				else{
					all ^= 1; // except that chain
					addBit((n&1)^1,n-2*two[n-1]+1);
				}
			}
			bool t = askBit(n&1,n);
			dep[n] = t^all; // init = 0
		}
		else{
			int y = readint();
			if(lst){
				y = n+1-y, x = n+1-x;
				x ^= y ^= x ^= y;
			}
			if(x == y){
				lst = copi[x];
				putchar(lst^48);
				putchar('\n');
				continue; // done
			}
			if(x == y-1){
				lst = !(copi[x]&copi[y]);
				lst ^= copi[x]; // range(x,x)
				putchar(lst^48);
				putchar('\n');
				continue; // done
			}
			if(copi[x] == 1) ++ x;
			else x += 2; // 0 and 1
			/** @note remember to add 1 to ans! */ ;
			lst = copi[y]; // if it ends with 1, minus 1
			if(one[y-1] >= y-x){ // cover
				if((y^x)&1) -- y, lst = 0;
			}
			else if(one[y-1]&1) -- y, lst = 0;
			lst ^= askBit(x&1,x)^dep[x];
			lst ^= askBit(y&1,y)^dep[y];
			/* cnt = edge^1 but init it's 1 */ ;
			putchar(lst^48), putchar('\n');
		}
	}
	return 0;
}
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-08-28 09:41:08  更:2021-08-28 09:41:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 18:42:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码