题解 | P8496 [NOI2022] 众数
P8496 [NOI2022] 众数
很少口口口形容一个题目。一道题目,我改换了三种假做法 \(\dots\)
抱怨部分(不想看直接往下翻就可以)
最开心的部分:
光速读题,高兴于一眼看出用链表维护原序列,权值线段树合并完成答案的维护,直接写出了非常 RE 的指针版,故改掉指针(0.5h)。
最上流的解法:
发现限制了 \(\sum C\) 的范围,想当然以为它的意思是线段树合并很安全,故考虑直接对于每一个操作三建立一颗临时树,在那上面查询,发现样例一下子就过了,高兴得很,提交,20 TLE + WA,不信邪,调试约半辈子(1.5h),来到下午,有人讲这道题。说这种做法很假,很破防,非要调出来这种做法,发现错在操作三节点没克隆而是直接修改了,遂大骂而改。然而,显然,操作三不懂人情世故,#11 12 13 17 18 19 20 并不会放过去这种躺尸做法。
最正确的做法(自认为):
结合同学讲解,注意到若一个数是绝对众数,则每次选择一点有大于 1/2 的概率选对,所以选的越多就越对,所以,我,直接,假设答案为 $ x = rand() \mod {n + q}$,使用最纯粹的随机拿下整整 10pts。
最天才的理解:
这样子做本来就只是随便搞搞,错了当然,要随机至少得先是里面的数才行,所以当然是在这些数里面随便找拉!~(T、Wa飞了),再次找到同学询问,同学说只要前11个数就可以了,所以高兴的更改,但还是错的,为什么呢。
阿,是我们天才的 旅游美食 听错了呢!实际上是只要前11个树的局部最优值。。。
总之虽然波折,至少该对了罢。。。。。。
不抽象的讲述正解
正确的做法大概已经在上面说过了。不喜欢看我牢骚可以直接跳到这里,简单概括就是用链表维护原序列,权值线段树合并完成答案的维护。权值线段树要维护本个链表中的众数(不是绝对众数),对于 \(1、2\) 直接加减修改就行,对于 \(3\) 则找出前几个数的局部最优值进行 \(check\),若存在满足绝对众数的数则会最多有 \(1/2^{k}\) 的概率出错。大概是前 11 个数能保证正确性("二分答案"出的结论)。对于 \(4\) 显然是链表和权值线段树的合并,注意合并前判定是不是空序列,否则会 75pts WA。于是题目结束,实现就放在下面了。注释有但不多(懒)。
#include <bits/extc++.h>
#define int long long
#define e_ putchar_unlocked(' ')
#define en_ putchar_unlocked('\n')
using namespace std;
inline int in() {
int n = 0; char p = getchar_unlocked();
while (p < '-') p = getchar_unlocked();
bool f = p == '-' ? p = getchar_unlocked() : 0;
do n = (n<<1) + (n<<3) + (p ^ 48), p = getchar_unlocked();
while (isdigit(p));
return f ? -n : n;
return n;
}
template <typename T> inline void in(T &n) { n = in();}
inline void out(int x) {
if(x < 0) putchar_unlocked('-'), x = -x;
if(x >= 10) out(x/10);
putchar_unlocked(x % 10 + '0');
}
constexpr int N = 1000000 + 10; const char me[] = "Ciallo~(∠・ω< )⌒ ★ ";
struct node {
int l, r;
int same, i;//same 指区间内出现最多的数出现的次数,i是内个数
} nod[N * 25];
int top;
inline int nnode() { top ++ ;return top;}
inline int clone(int t) {
nod[++top] = nod[t];
return top;
}
inline void up(int u) {
nod[u].same = max(nod[nod[u].l].same ,nod[nod[u].r].same);
if(nod[u].same == nod[nod[u].l].same) nod[u].i = nod[nod[u].l].i;
else nod[u].i = nod[nod[u].r].i;
}
#define ul nod[u].l
#define ur nod[u].r
#define usame nod[u].same
#define lsame nod[ul].same
#define rsame nod[ur].same
inline void insert(int&u, int l, int r, int val) { // 插入 - 权值线段树
if(!u) u = nnode();
if(l == r) { usame ++; nod[u].i = l; return;}
int m = (l+r) >> 1;
if(m >= val) insert(ul, l, m, val);
else insert(ur, m+1, r, val);
up(u);
}
inline void delate(int u, int l, int r, int val) { // 删除 - 权值线段树
if(l == r) { usame --; return;}
int m = (l+r) >> 1;
if(m >= val) delate(ul, l, m, val);
else delate(ur, m+1, r, val);
up(u);
}
struct mylist {
int data;
int pre, nxt;//链表
inline bool end() { return !nxt;}
inline bool begin() { return !pre;}
} lis[N * 20];
int topl;
inline int nlist() {
return ++topl;
}
inline void ins(int u, int&me) { // 插入 - 链表
if(!me) { me = nlist(); lis[me].data = u; return;}
while(!lis[me].end()) me = lis[me].nxt;
lis[me].nxt = nlist();
lis[lis[me].nxt].data = u, lis[lis[me].nxt].pre = me;
me = lis[me].nxt;
}
inline void del(int&me) { // 删除 - 链表
lis[me].nxt = 0;
me = lis[me].pre;
lis[lis[me].nxt].pre = 0;
lis[me].nxt = 0;
}
struct tree {
int rot, siz;// siz 用链表维护会更快
int en, be;//链表头尾
inline void addnum(int val) {
insert(rot, 0, N - 1, val);
ins(val, en);
siz++;
if(siz == 1) be = en;
}
inline void delnum() {
while(!lis[en].end()) en = lis[en].nxt;
delate(rot, 0, N - 1, lis[en].data);
del(en);
siz--;
}
} tr[N]; // 方便维护多棵树
inline void merge(int&a, int&b, int l, int r) {
if(!a) { a = b; return;}
if(!b) return;
if(l == r) { nod[a].same += nod[b].same; return;}
int m = (l+r) >> 1;
merge(nod[a].l, nod[b].l, l, m);
merge(nod[a].r, nod[b].r, m+1, r);
up(a);
}
inline void merge_tr(tree a, tree b, tree&A) { // 合并序列
if(!a.siz && !b.siz) return;
if(a.siz == 0) { A = b; return;} // 特判!!!
if(b.siz == 0) { A = a; return;}
A = a;
merge(A.rot ,b.rot , 0, N-1);
while(!lis[A.en].end()) A.en = lis[A.en].nxt;
while(!lis[b.en].end()) b.en = lis[b.en].nxt;
while(!lis[b.be].begin()) b.be = lis[b.be].pre;
lis[A.en].nxt = b.be;
lis[b.be].pre = A.en;
A.en = b.en;
A.siz += b.siz;
}
inline int query(int u, int l, int r, int val) {
if(l == r) return nod[u].same;
int m = (l+r) >> 1;
if(m >= val) return query(ul, l, m, val);
else return query(ur, m+1, r, val);
}
inline int que(int y) { // 万恶的询问
int msz = 0;
vector<int>tmp;
for(int i = 1; i <= y; i ++) tmp.push_back(in());
for(int v : tmp) msz += tr[v].siz;
msz = msz / 2 + 1;
int cnt = 0;
for(int v : tmp) {
if(cnt++ > 13) break;
int sm = 0, x = nod[tr[v].rot].i;
for(int g : tmp) {
sm += query(tr[g].rot, 0, N-1, x);
}
if(sm >= msz) return x;
}
return -1;
}
struct momo {
void excute(const char *me) {
int n = in(), q = in();
for(int i = 0; i <= n+q; i++) tr[i].rot = nnode();
for(int i = 1; i <= n; i ++) {
int l = in();
for(int j = 1, x; j <= l; j ++) {
in(x);
tr[i].addnum(x);
}
}
// tr[1].addnum(0);
// merge_tr(tr[1], tr[2], tr[5]);
// tr[5].addnum(223);
// for(int i = 1; i <= 124; i++) {
// tr[5].addnum(124);
// }
// for(int i = 1; i <= 123; i ++) tr[3].addnum(i);
// merge_tr(tr[3], tr[5], tr[112]);
// out(tr[112].siz);
for(int i = 1, op, x, y, j; i <= q; i ++) {
in(op);
switch(op) {
case 1 :{
in(x), in(y);
tr[x].addnum(y);
break;
}
case 2 :{
in(x);
tr[x].delnum();
break;
}
case 3 :{
in(y);
out(que(y)), en_;
break;
}
case 4 :{
in(x), in(y), in(j);
merge_tr(tr[x], tr[y], tr[j]);
break;
}
}
}
}
} world;
signed main() {
world.excute(me);
}

浙公网安备 33010602011771号