[Contest on 2021.8.26] 打脸,有时是在一瞬之间

( ext{CF1045G AI robots})

关于这道题

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

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

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

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

哭了。但是我是真不会

解法

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

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

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

维护 (u-vle 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,'
');
	return 0;
}

( ext{NAND})

题目描述

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

操作分为以下两种:

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

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

解法

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

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

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

[ans_x=ans_{a,x}oplus (val_{a,x} ext{ nand }a_{ ext{mid}+1})oplus...oplus (val_{a,x} ext{ nand }a_{ ext{mid}+1} ext{ nand }... ext{ 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 ext{ nand }igoplus_{i=1}^n v_i)打表发现

  • (n) 为奇数时。( ext{Ans}=igoplus_{i=1}^n a ext{ nand }v_i)
  • (n) 为偶数时。( ext{Ans}=!igoplus_{i=1}^n a ext{ 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],'
');
				continue;
			}
			node r=ask(1,1,T,x+1,y);
			print(lst=r.ans[a[x]]^a[x],'
');
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/15192331.html