\(\text{CF1045G AI robots}\)

关于这道题

模拟赛的时候,旁边的 \(\sf{ET}\) 秃然说:"这不是 \(\rm cdq\) 分治吗?"

我当机立断地否定了她的想法,并且表达了 "你写出来算我输" 的意愿。然后…

键盘啪啪的,很快啊!她就把这题切了!

我则 "点到为止",写了个线段树优化建图,关键是好像是错的…

哭了。但是我是真不会

解法

"两个机器人互相看见" 这个条件是很难维护的,因为 \(\rm cdq\) 分治只支持单向的查询。

别急着放弃啊!我们能否将 "双向" 改成 "单向" 呢?考虑两个机器人 \(i,j\),如果 \(r_i>r_j\)\(j\) 能够看到 \(i\),那么 \(i\) 一定能看到 \(j\)!这样我们将 \(r_i\) 从大到小排序,就转化成了 "单向" 的问题。

我们可以设想一下:在算法内部,右半边的 \(r\) 都小于左半边,每个半边则满足 \(q\) 从小到大排序。用双指针将智商合法的左半边机器人加入某个数据结构,在右半边查询。显然,在将机器人加入数据结构时,应当加入 \(x\) 坐标,方便后面的查询。所以我们还要预处理一下每个机器人能看到的区间范围。

维护 \(u-v\le k\) 很好维护,\(|u-v|\le k\) 呢?其实是类似的。 就像这样:

while(L<=mid and s[i].q-s[L].q>k)
	add(s[L].x,-1),++L;
while(R<mid and s[R+1].q-s[i].q<=k)
	++R,add(s[R].x,1);

就算 s[R+1].q-s[i].q 是负数且相距 \(>k\),前面的 while() 实际上已经减去这种情况了。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

#include <algorithm>
using namespace std;

const int maxn=1e5+5;

int n,k,X[maxn],len;
int c[maxn];
long long ans;
struct node {
	int x,R,q,l,r;
	
	bool operator < (const node &t) const {
		return R>t.R;
	}
} s[maxn];

bool cmp(node a,node b) {
	return a.q<b.q;
}

int lowbit(int x) {
	return x&-x;
}

void add(int x,int k) {
	while(x<=len)
		c[x]+=k,
		x+=lowbit(x);
}

int ask(int x) {
	int r=0;
	while(x)
		r+=c[x],
		x-=lowbit(x);
	return r;
}

void cdq(int l,int r) {
	if(l==r) return;
	int mid=l+r>>1;
	cdq(l,mid); cdq(mid+1,r);
	int L=l,R=l-1;
	for(int i=mid+1;i<=r;++i) {
		while(L<=mid and s[i].q-s[L].q>k)
			add(s[L].x,-1),++L;
		while(R<mid and s[R+1].q-s[i].q<=k)
			++R,add(s[R].x,1);
		ans+=ask(s[i].r)-ask(s[i].l-1); 
	}
	for(int i=L;i<=R;++i)
		add(s[i].x,-1);
	sort(s+l,s+r+1,cmp);
}

int main() {
	n=read(9),k=read(9);
	for(int i=1;i<=n;++i)
		X[i]=s[i].x=read(9),
		s[i].R=read(9),
		s[i].q=read(9);
	sort(s+1,s+n+1);
	sort(X+1,X+n+1);
	len=unique(X+1,X+n+1)-X-1;
	for(int i=1;i<=n;++i) {
		s[i].l=lower_bound(X+1,X+len+1,s[i].x-s[i].R)-X;
		s[i].r=upper_bound(X+1,X+len+1,s[i].x+s[i].R)-X-1;
		s[i].x=lower_bound(X+1,X+len+1,s[i].x)-X;
	}
	cdq(1,n);
	print(ans,'\n');
	return 0;
}

\(\text{NAND}\)

题目描述

初始时你有一个空序列,之后有 \(n\) 个操作。

操作分为以下两种:

  • 在序列末尾插入一个元素 \(x\)
  • 定义与非运算:\(a\text{ nand }b=!(a\text{ and }b)\)\(\text{nand}(l,r)\)\(l\)\(r\) 的与非和。询问 \(\bigoplus_{i=l}^r \text{nand}(l,i)\)

强制在线\(n\le 4\cdot 10^6\)。操作一个数 \(\le 3900000\),操作二个数 \(\le 10^5\)

解法

\(\rm md\)\(\rm std\) 的代码真的不当人啊!真的是什么不好懂写什么。哭了。

首先肯定想到用线段树维护,但是打表发现,与非运算没有结合律,考虑到本题数字只有 \(0,1\),我们不妨枚举首个数字 \(x\),维护 \(val_o=x\text{ nand }a_l\text{ nand }a_{l+1}\text{ nand }...\text{ nand }a_r\)

合并两个区间 \(a,b\),就有:

\[ans_x=ans_{a,x}\oplus (val_{a,x}\text{ nand }a_{\text{mid}+1})\oplus...\oplus (val_{a,x}\text{ nand }a_{\text{mid}+1}\text{ nand }...\text{ nand } a_r) \]

\[=ans_{a,x}\oplus ans_{b,val_{a,x}} \]

这样就可以合并了。

不过,操作一的个数看上去就是来卡你的 \(\log\) 的,我们能否将它的复杂度减到 \(\mathcal O(1)\) 呢?

while(o!=1 and (o&1)) {
	o>>=1;
	t[o]=Merge(t[o<<1],t[o<<1|1]);
}

当这个节点是右儿子的时候,就一直往上合并。这样一路向上合并的时候,顺带把已经合并好的左儿子一起合并。由于每个点只会被合并一次,所以均摊是 \(\mathcal O(1)\) 的。

不过为啥这是对的捏?考虑题目的性质:你插好多,它问好多。所以没被右儿子更新的区间此时一定查不完整,所以查询时不会出错。


另外再提一嘴与非运算对异或运算的分配律问题。当时就是被这个坑了很久

即计算 \(a\text{ nand }\bigoplus_{i=1}^n v_i\)打表发现

  • \(n\) 为奇数时。\(\text{Ans}=\bigoplus_{i=1}^n a\text{ nand }v_i\)
  • \(n\) 为偶数时。\(\text{Ans}=!\bigoplus_{i=1}^n a\text{ nand }v_i\)

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

#include <iostream>
using namespace std;

const int maxn=4e6+5;

struct node {
	int val[2],ans[2];
} t[maxn<<2];
int p[maxn],a[maxn];

node Merge(node a,node b) {
	node r;
	r.val[0]=b.val[a.val[0]];
	r.val[1]=b.val[a.val[1]];
	r.ans[0]=a.ans[0]^b.ans[a.val[0]];
	r.ans[1]=a.ans[1]^b.ans[a.val[1]];
	return r;
}

node ask(int o,int l,int r,int L,int R) {
	if(l>=L and r<=R) return t[o];
	int mid=l+r>>1;
	if(R<=mid) return ask(o<<1,l,mid,L,R);
	if(L>mid) return ask(o<<1|1,mid+1,r,L,R);
	node f=ask(o<<1,l,mid,L,R);
	f=Merge(f,ask(o<<1|1,mid+1,r,L,R));
	return f;
}

void build(int o,int l,int r) {
	if(l==r) return (void)(p[l]=o);
	int mid=l+r>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
}

int main() {
	int lst=0,op,x,y,n=0,T=read(9);
	build(1,1,T);
	for(int i=1;i<=T;++i) {
		op=read(9),x=read(9);
		if(op==1) {
			x^=lst;
			a[++n]=x;
			int o=p[n];
			if(!x) {
				t[o].val[0]=t[o].val[1]=1;
				t[o].ans[0]=t[o].ans[1]=1;
			}
			else {
				t[o].val[0]=t[o].ans[0]=1;
			}
			while(o!=1 and (o&1)) {
				o>>=1;
				t[o]=Merge(t[o<<1],t[o<<1|1]);
			}
		}
		else {
			y=read(9);
			if(lst) {
				x=n-x+1;
				y=n-y+1;
				swap(x,y);
			}	
			if(x==y) {
				print(lst=a[x],'\n');
				continue;
			}
			node r=ask(1,1,T,x+1,y);
			print(lst=r.ans[a[x]]^a[x],'\n');
		}
	}
	return 0;
}
posted on 2021-08-27 17:01  Oxide  阅读(43)  评论(0编辑  收藏  举报