-
题目: 妄想集合
-
题意:给出\(n(1\leq n \leq 10^5)\)个可重集合,开始每个集合给出一个数\(a_i(1\leq a_i\leq 10^9)\),有\(m(1\leq m \leq 10^5)\)次操作。共有两种操作:
\(\text{Quant l r x}\):往编号在\(l\sim r\) 的每个集合中加入一个数 x。
\(\text{Ask l r}\):询问能否从 \(l\sim r\) 的集合中取出三个数使得他们能作为边长组成一个三角形(即最小两个和要大于最大的)。
对于每个 \(\text{Ask}\) 操作,输出一行表示答案,能则输出 YES ,否则输出 NO。
-
思路:线段树、思维、剪枝。
-
解析:若希望一些数中找不到任意一组数(\(1\sim 10^9\))能组成一个三角形,那么又希望这些数的数量尽可能长的情况下只有斐波那契数列正好是它的极限,而斐波那契数列第四十五项已经超过\(10^9\),所以我们可以特判询问的一个区间的所有集合中的元素总个数大于或等于45时肯定能找到一组能组成三角形的数,否则可以直接暴力枚举这个区间所有集合中的元素,最多也不超过45个(枚举前从大到小排个序,原因不说了)。
找到突破口之后就是利用线段树维护一个区间集合的元素个数,这里还需要加一个小标记\(tag\),0:表示该区间集合个数 \(\leq 45\),反之...,这个小标记是为了在区间修改剪枝而用的,因为区间修改一般而言都要懒标记来降低时间复杂度嘛,所以这里可以通过一个剪枝来判断是否还有必要进行对该子树的修改。这种线段树的剪枝第一次遇见,相当于在pushup中每次将子节点的标记进行求交,判断是否存在一棵子数的区间所有集合的元素总个数大于或等于45,若是这样,它的父节点在下一次被进行更新操作时根本就不需要继续往下更新。
-
代码:
#include<iostream> #include<cstdio> #include<vector> #include<algorithm> using namespace std; const int N = 1e5 + 5; const int M = 45; int n, m; vector<int> v[N]; struct Node { int l, r; int cnt = 0; //该区间集合的结点总个数 int tag = 0; //0:表示该区间集合个数 < M }tr[4 * N]; void pushup(int u) { tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt; tr[u].tag = tr[u << 1].tag && tr[u << 1 | 1].tag; //最终判断父节点的tag即可 } void build(int u, int l, int r) { tr[u] = {l, r}; if(l == r) { if(v[l][0]) { tr[u].cnt = 1; tr[u].tag = 0; } return; } int mid = l + r >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); pushup(u); } int query(int u, int l, int r) { if(l <= tr[u].l && r >= tr[u].r) return tr[u].cnt; int mid = tr[u].l + tr[u].r >> 1; int cnt = 0; if(l <= mid) cnt = query(u << 1, l, r); if(r > mid) cnt += query(u << 1 | 1, l, r); return cnt; } void update(int u, int l, int r, int x) //区间修改 但部分可能仅仅是单点修改甚至不修改 { if(tr[u].l >= l && tr[u].r <= r && tr[u].tag) //该区间所有集合的元素个数均大于M return; if(tr[u].l == tr[u].r) { tr[u].cnt ++; v[tr[u].l].push_back(x); //单点修改 if(tr[u].cnt >= M) tr[u].tag = 1; //判断该集合元素个数是否>=45 return; } int mid = tr[u].l + tr[u].r >> 1; if(l <= mid) update(u << 1, l, r, x); if(r > mid) update(u << 1 | 1, l, r, x); pushup(u); } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) { int x; scanf("%d", &x); v[i].push_back(x); } build(1, 1, n); while(m --) { char op[10]; int l, r, x; scanf("%s", op); if(*op == 'Q') { scanf("%d%d%d", &l, &r, &x); update(1, l, r, x); } else { scanf("%d%d", &l, &r); int cnt = query(1, l, r); if(cnt >= 45) printf("YES\n"); else if(cnt < 3) printf("NO\n"); else { vector<int> temp; temp.clear(); int flag = 0; for(int i = l; i <= r; i++) { for(int j = 0; j < v[i].size(); j++) { temp.push_back(v[i][j]); } } sort(temp.begin(), temp.end()); for(int i = 0; i < temp.size() - 2; i++) { int a = temp[i], b = temp[i + 1], c = temp[i + 2]; if(a + b > c) { flag = 1; break; } } if(flag) printf("YES\n"); else printf("NO\n"); } } } return 0 ; }
牛客练习赛90 D.妄想集合(线段树、剪枝)
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。

提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
GaussDB(for MySQL)剪枝功能,让查询性能提升70倍!
如何通过MySQL提升DISTINCT,尤其是多表连接下DISTINCT的查询效率?
MySQL 表连接 执行效率 DISTINCT SQL语句 -
牛客练习赛36 D.Rabbit的数列(线段树)#define i++ #include
-
牛客练习赛64 D.宝石装箱(容斥+dp)
传送门不会写,完全搬的官方题解,我是fw定义g[k]g[k]g[k]为选出kkk个盒子装不合法的方案数设
i++ #define #include -
牛客练习赛18
A 题目描述 这题要你回答T个询问,给你一个正整数S,若有若干个正整数的和为S,则这若干的数的乘积最大是多少?请输出答
#include 简单多边形 ide