2022 NOI Online 游记 + 赛后复盘
人生中 第一场OI全国大赛
感觉还不错 虽然结果一般(还没出成绩 估分很低)
但也毕竟是个“练兵”
是为了日后在比赛中取得更好的成绩
打好基础 埋下伏笔
8:30开始
8:27左右就能进了
T1
丹钓战
谐音梗是要扣钱的(bushi
正巧了 25号老师刚带我们练了一套题 第一题就是用单调栈去写的矩阵题 一眼就看出来了谐音
(然鹅某条金鱼并没有get到谐音的点并以丹钓战为题脑嗨了一万三千字的唯美故事)
看了一会 N能达到5e5 感觉要写线段树
线段树打了1个多小时 打不出来 没办法 直接转暴力 15pts
复盘:
受到启发:区间 ——> 莫队
写了个莫队
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cmath> 5 using namespace std; 6 #define int long long 7 const int N = 5e5 + 5; 8 inline int read(){ 9 int s = 0,w = 1; 10 char ch = getchar(); 11 while(ch<'0'||ch>'9'){ 12 if(ch=='-')w = -1; 13 ch = getchar(); 14 } 15 while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar(); 16 return s * w; 17 } 18 inline void write(int x){ 19 static char buf[16]; 20 static int len = -1; 21 if(x < 0) putchar('-'),x = -x; 22 do buf[++ len] = x % 10,x /= 10; 23 while(x); 24 while(len >= 0) putchar(buf[len --] + '0'); 25 putchar('\n'); 26 } 27 struct A{ 28 int l; 29 int r; 30 int num; 31 }q[N]; 32 int n,Q; 33 int a[N]; 34 int b[N]; 35 int rcd[N] = {0,0}; 36 int ans[N]; 37 int pos[N]; 38 int nw = 1; 39 class CHK{ 40 public: 41 inline void Add_L(){ 42 l--; 43 vis[rcd[l]]++; 44 sum++; 45 sum -= vis[l]; 46 } 47 inline void Sub_L(){ 48 vis[rcd[l]]--; 49 sum--; 50 sum += vis[l]; 51 l++; 52 } 53 inline void Add_R(){ 54 r++; 55 vis[rcd[r]]++; 56 sum += (rcd[r] < l); 57 } 58 inline void Sub_R(){ 59 vis[rcd[r]]--; 60 sum -= (rcd[r] < l); 61 r--; 62 } 63 inline int L(){ 64 return l; 65 } 66 inline int R(){ 67 return r; 68 } 69 inline int Sum_(){ 70 return sum; 71 } 72 private: 73 int vis[N]; 74 int l = 1,r = 0; 75 int sum = 0; 76 }; 77 CHK Stack_;//最近迷上了class 78 signed main(){ 79 n = read(); 80 Q = read(); 81 int siz = sqrt(n); 82 for(register int i = 1;i <= n;i++) a[i] = read(); 83 for(register int i = 1;i <= n;i++) b[i] = read(); 84 for (register int i = 2;i <= n;i ++,nw = i - 1){ 85 while(nw){ 86 if (a[i] == a[nw] || b[i] >= b[nw]) nw = rcd[nw]; 87 else break; 88 } 89 rcd[i] = nw; 90 } 91 for(register int i = 1;i<=Q;i ++){ 92 q[i].l = read(); 93 q[i].r = read(); 94 q[i].num = i; 95 pos[i] = (q[i].l - 1)/(siz + 1);//收到大佬启发;i/siz只能过60pts 96 } 97 sort(q + 1,q + 1 + Q,[](A a,A b){return pos[a.num] != pos[b.num]?a.l < b.l : ((pos[a.num] & 1) ? a.r < b.r : a.r > b.r);});//在B站学到的高级cmp写法 98 for(register int i = 1;i<=Q;i ++){ 99 while(q[i].l < Stack_.L()) Stack_.Add_L(); 100 while(q[i].r > Stack_.R()) Stack_.Add_R(); 101 while(q[i].l > Stack_.L()) Stack_.Sub_L(); 102 while(q[i].r < Stack_.R()) Stack_.Sub_R(); 103 ans[q[i].num] = Stack_.Sum_(); 104 } 105 for(register int i = 1;i<=Q;i ++) write(ans[i]); 106 return 0; 107 }
T2
讨论
乍一看以为是图论
若该点入度大于2 则被两个以上的人会 即被两个以上的点所指向 那就查这些点的出度 出度>2则可能有不会的题 为了控复杂度 用string去存每个人会的题 s.find()找包含(事实证明这是一种很扯淡的做法)
这样存图 每个人的编号既是一个人 也是一道题 一举两得
但是肯定是什么地方写的不对 访问溢出 全 RE
复盘:
从头开始推
如果一个人参与了讨论,则它必然会与另一个人有同样会的题目;
按会的题量降序排序,若上下两人有共同的题目,上面的人会的题数>下面的人,所以一定会有下面不会的题;
则只需判断下面的人是否有上面的人不会的题即可;
与记入度同理 记录每道题“被会”的数量 若数量 == 1,则它是只被当前的人会,属于新题;
若数量 >= 2,则对于当前的人来说,他与下面的那个人就不会这道题,则他俩是可以开始讨论的
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <limits.h> 6 using namespace std; 7 const int N = 5e5 +5; 8 inline int read(){ 9 int dp = 0,w = 1; 10 char ch = getchar(); 11 while(ch<'0'||ch>'9'){ 12 if(ch=='-')w = -1; 13 ch = getchar(); 14 } 15 while(ch >= '0' && ch <= '9') dp = dp * 10 + ch - '0',ch = getchar(); 16 return dp * w; 17 } 18 inline void write(int x){ 19 static char buf[16]; 20 static int len = -1; 21 if(x < 0) putchar('-'),x = -x; 22 do buf[++ len] = x % 10,x /= 10; 23 while(x); 24 while(len >= 0) putchar(buf[len --] + '0'); 25 putchar('\n'); 26 } 27 int T; 28 int n,p; 29 int x = -1,y = -1; 30 int t,v; 31 struct A{ 32 int pos; 33 int num; 34 }ans[N]; 35 int a[N]; 36 int f[N]; 37 int dp[N] = {0}; 38 bool chk = 0; 39 int main() { 40 T = read(); 41 while (T--) { 42 chk = 0; 43 memset(f,0,sizeof(f)); 44 n = read(); 45 for (register int i = 1; i <= n; i++) { 46 ans[i].pos = i; 47 ans[i].num = read(); 48 dp[i] = dp[i - 1] + ans[i].num; 49 for (register int j = 1;j<=ans[i];j++) a[dp[i-1]+j] = read(); 50 } 51 sort(ans + 1,ans + n + 1,[](A a,A b){return a.num > b.num;}); 52 for(register int i = 1;i<=n;i++,x = -1,y = -1){ 53 p = ans[i].pos; 54 for(register int j = 1;j<=ans[p].num;j++){ 55 v = f[a[dp[p-1]+j]]; 56 if(!v) v = p; 57 if(!x) x = v; 58 else if(x != v) y = v; 59 if(x != -1 && y != -1){ 60 chk = 1; 61 ans[p].num = -INT_MAX; 62 } 63 f[a[dp[p-1]+j]] = p; 64 } 65 } 66 if(chk) puts("NO"),cout<<endl; 67 else{ 68 if(ans[x].num > ans[y].num) x = p; 69 else y = p; 70 puts("YES"); 71 cout<<endl; 72 printf("%d %d\n",x,y); 73 } 74 } 75 return 0; 76 }
T3
如何正确的去排序
考试的时候没有时间看了 暴力都没时间写 直接打表上样例 估计是保龄
复盘:
第一反应还是线段树
但是感觉线段树好麻烦
于是决定用树状数组
上了趟博客园 看了hbc大佬的博客 提醒我了
CDQ分治
老师之前在群里发过一个讲陈丹琦的文章 里面就提到了她的cdq分治
于是就去学了cdq分治
并引申到二维偏序三维偏序
才发现 原来丹钓战也是二维偏序
后来一直在推公式 直到发现m = 4时max(S4)可以被压掉 压回三维
去查了知道 这个东西叫反演
所以整个就是二维偏序+三维偏序(著名的陌上开花)+树状数组+cdq分治+反演(这个实在想不到)
(看到好多人还有写 离散化 容斥)
然后就 疯狂的 TLE + WA
好在看到了一位大佬的博客
才知道位运算这么好用
还有处理双倍贡献
于是
终于
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <limits.h> 5 #include <cstring> 6 #include <cmath> 7 //整个代码是在第一次系的树状数组 + cqd + 三维偏序的基础上 8 //从大佬的代码中学习了好多降复杂度的写法 包括区域定义、快速位运算等等 9 //整个复杂度在O(nlog2n)左右 10 #define MOR 996655884 11 #define lowbit(x) x & -x 12 #define int long long 13 using namespace std; 14 const int N = 5e5 + 5; 15 int n,m; 16 int a[5][N]; 17 inline int read(){ 18 int s = 0,w = 1; 19 char ch = getchar(); 20 while(ch<'0'||ch>'9'){ 21 if(ch=='-')w = -1; 22 ch = getchar(); 23 } 24 while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar(); 25 return s * w; 26 } 27 inline void write(int x) { 28 static char buf[16]; 29 static int len = -1; 30 if(x < 0) putchar('-'),x = -x; 31 do buf[++len] = x % 10,x /= 10; 32 while(x); 33 while(len >= 0) putchar(buf[len--] + '0'); 34 putchar('\n'); 35 } 36 const int INF_ = 2e5; 37 struct A{ 38 int rcd[4]; 39 int p; 40 int res; 41 friend bool operator < (const A &a,const A &b); 42 }; 43 inline bool operator < (const A a, const A b){ 44 for(register int i = 0;i < 3;i++) if(a.rcd[i] ^ b.rcd[i]) return a.rcd[i] < b.rcd[i]; 45 return a.p < b.p; 46 }//看了大佬的处理方式 重载运算符 47 //原先只从一维重定义rcd,m == 4的时候就全失效了 48 class C{ 49 private: 50 int s[N*2]; 51 A t[N*2]; 52 int res = 0; 53 public: 54 inline void modify(int x,int y){ 55 for(x += INF_; x < INF_ << 1; x += lowbit(x)) s[x] += y; 56 } 57 inline int query(int x){ 58 res = 0; 59 for(x += INF_; x; x -= lowbit(x)) res += s[x]; 60 return res; 61 } 62 void cdq(int l,int r,A *f){ 63 if(l == r) return; 64 /*if(l < r) return;*/ 65 int mid = l + r >> 1; 66 cdq(l,mid,f); 67 cdq(mid + 1,r,f);//二分 68 static A t[N*2];//这步真的好重要 69 int h = 0; 70 for(register int i = l,j = mid + 1;(i <= mid) || (j <= r);) 71 if(i <= mid && (j > r || f[i].rcd[1] <= f[j].rcd[1])){ 72 f[i].p == -1 && (modify(f[i].rcd[2],1),0);//位运算压时间真的超好用 73 t[h] = f[i]; 74 h++,i++; 75 } 76 else{ 77 if (j <= r && (i > mid || f[i].rcd[1] > f[j].rcd[1])) { 78 ~f[j].p && (f[j].res += query(f[j].rcd[2])); 79 t[h] = f[j]; 80 h++,j++; 81 } 82 } 83 for(register int i = l;i <= mid;i++) f[i].p == -1 && (modify(f[i].rcd[2],-1),0); 84 int k = 0; 85 for(register int i = l;i <= r;i++){ 86 f[i] = t[k]; 87 k++; 88 } 89 } 90 inline int work(int n,int k,A *f){ 91 sort(f + 0,f + n,[](A a,A b){for(register int i = 0;i < 3;i++)if(a.rcd[i] ^ b.rcd[i])return a.rcd[i] < b.rcd[i];return a.p < b.p;}); 92 cdq(0,n - 1,f);//从头开始 从第一位开始 向下分治 93 res = 0; 94 for(register int i = 0;i < n;i++){ 95 ~f[i].p && (res += a[k][f[i].p] * f[i].res);//'~'第一次用 反演 96 f[i].res = 0; 97 } 98 return res; 99 } 100 }; 101 C cdq; 102 inline int solve(){ 103 int res = 0,h = 0; 104 static A f[N];//这步真的好重要 105 for(register int i = 0;i < m;i++,h = 0){ 106 for(register int k = 0; k < n; k++){ 107 f[h].p = -1; 108 h++; 109 for(register int j = 0,p = 0;j < m;j++){ 110 if(j == i) continue; 111 f[h].rcd[p] = a[i][k] - a[j][k]; 112 f[h ^ 1].rcd[p] = a[j][k] - a[i][k] + (j < i);//这步第一次写的的时候用了两个if - else 有好多重复贡献 会耽误掉很多时间 113 p++; 114 } 115 f[h].p = k; 116 h++; 117 } 118 res += cdq.work(h,i,f); 119 } 120 return res; 121 } 122 signed main(){ 123 m = read(); 124 n = read(); 125 for(register int i = 0;i < m;i++) for(register int j = 0;j < n;j++) a[i][j] = read(); 126 int res = solve(); 127 for(register int i = 0;i < m;i++) for(register int j = 0;j < n;j++) a[i][j] *= -1; 128 int ans = res - solve() << 1; 129 write(ans); 130 return 0; 131 }
Generally:
我就是那个考试连暴力都没时间写的蒟蒻
加油吧 小伙子
24OI Fighting!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通