1010考试T3
1010考试T3
题目大意:
有一个序列有两个操作:
\(1 \ [l, r]\) 表示在区间\([l, r]\)中选出两个非空集合,使这两个集合没有交集。如果可以找到输出\(Yes\),否则输出\(No\)。
\(2 \ [l, r]\)表示将区间内所有数\(a[i]\)变为\(a[i] ^ 3 \% v\) 。
\(n <= 100000, v <= 1000\)
线段树。
看完题解觉得这题也还行,但考场上就是不会做。
先看操作一怎么搞:
我们可以知道集合的个数为:\(2 ^ {len}\),\(len\)表示区间里有几个数,还可以知道区间所有数的总和为\([0, len * v]\)。根据抽屉原理,可以知道:如果\(2 ^ {len} > len *v + 1\),那么肯定会有两个集合的总和相同,所以只要这个判断条件成立,我们就可以直接输出\(Yes\)。如果说这两个集合有交集怎么办?其实只要把这两个集合的交集都去掉,剩下的数的总和还是相等的,抽屉原理依然正确。
那如果不成立呢?因为\(v <= 1000\)我们可以解出\(len >= 14\)是成立,那么我们最多就会暴力找13个数,直接\(dfs\),先搜前一半,搜后一半,最坏复杂度是\(3 ^ 6 + 3 ^ 7\),加上一些剪枝根本达不到这么多。
再看操作二怎么搞:
让线段树维护一个二操作的次数,每次下放到叶子节点在修改,因为最多改13个数,复杂度就可以过。可是每次暴力乘三次方太慢了,我们发现\(v\)的范围很小,所以可以预处理出\([0, 1000)\)所有数的三次方是多少,用倍增预处理就好了。\(f[i][0] = i * i *i \% v\) \(f[i][j] = f[f[i][j - 1]][j - 1]\)
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int n, m, v, opt, l, r, p, cnt;
int a[N], sta[N << 2], f[1005][21], tag[N << 2];
bool vis[N];
void make_pre() {
for(int i = 0;i < v; i++) f[i][0] = i * i % v * i % v;
for(int j = 1;j <= 20; j++)
for(int i = 0;i < v; i++) f[i][j] = f[f[i][j - 1]][j - 1]; //倍增预处理
}
void down(int o) {
if(tag[o]) tag[ls(o)] += tag[o], tag[rs(o)] += tag[o], tag[o] = 0;
}
void up(int &a, int res) {
int i = 20;
while(i >= 0) {
if(res >= (1 << i)) {
res -= (1 << i);
a = f[a][i];
}
if(res == 0) return ;
i --;
}
}
void change(int o, int l, int r, int x, int y) {
if(x <= l && y >= r) { tag[o] ++; return ; }
down(o);
if(x <= mid) change(ls(o), l, mid, x, y);
if(y > mid) change(rs(o), mid + 1, r, x, y);
}
void query(int o, int l, int r, int x) {
if(l == r) { up(a[x], tag[o]); tag[o] = 0; return ; }
down(o);
if(x <= mid) query(ls(o), l, mid, x);
if(x > mid) query(rs(o), mid + 1, r, x);
}
void dfsl(int now, int to, int sum, int k) {
if(p) return ;
if(now == to + 1) {
if(k) {
if(!sum) p = 1; //如果找到一个非空集合的总和为0, 说明这个集合可以分为两个总和相同的集合
else if(!vis[sum] && sum >= 0) vis[sta[++ cnt] = sum] = 1; // 存一下出现过的数,在下次dfs里找有没有这些数。
}
return ;
}
dfsl(now + 1, to, sum, k);
dfsl(now + 1, to, sum + a[now] + 1, 1);
dfsl(now + 1, to, sum - a[now] - 1, 1);
}
void dfsr(int now, int to, int sum, int k) {
if(p) return ;
if(now == to + 1) {
if(k) {
if(!sum) p = 1;
else if(vis[sum] && sum >= 0) p = 1;
}
return ;
}
dfsr(now + 1, to, sum, k);
dfsr(now + 1, to, sum + a[now] + 1, 1); //这里加一减一是因为题面说每个小朋友x的花费是a[x] + 1
dfsr(now + 1, to, sum - a[now] - 1, 1);
}
int main() {
n = read(); m = read(); v = read();
for(int i = 1;i <= n; i++) a[i] = read();
make_pre();
for(int i = 1;i <= m; i++) {
opt = read(); l = read(); r = read();
if(opt == 2) { change(1, 1, n, l, r); }
else {
int len = r - l + 1;
if(pow(2, len) > len * v - len + 1) { printf("Yes\n"); continue; }
else {
for(int j = l;j <= r; j++) query(1, 1, n, j);
p = cnt = 0;
dfsl(l, mid, 0, 0); dfsr(mid + 1, r, 0, 0);
for(int j = 1;j <= cnt; j++) vis[sta[j]] = 0;
if(p) printf("Yes\n"); else printf("No\n");
}
}
}
fclose(stdin); fclose(stdout);
return 0;
}