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