\(\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\),就有:
这样就可以合并了。
不过,操作一的个数看上去就是来卡你的 \(\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;
}