过了一周,终于迎来了第二次模拟(这不是期待的语气),看第一周毒瘤程度,我就觉得接下来的模拟只能更毒瘤。
花了10多分钟读完了三道题,觉得暴力还是挺好写的,然后在每一道题都思索那么几分钟后,觉得还是写暴力靠谱一些……不过还好,一个半点就把暴力都写完了。
然后我就接着想正解……
T1 game
这道题刚开始就觉得是二维线段树,然后瞟了一眼数据范围:1e6 * 1e6,于是就犹豫了。就一直在想能不能行和列各只开一个线段树,最终反正是没想出来,于是开始优化我的暴力。推了半天发现了一个惊天秘密:就是修改的顺序是可以颠倒的:于是我就先O(n)处理了所有行的修改,然后对于所有列的修改,再暴力的O(n2)修改:所以说,只要这个数据行的修改越多,我就越容易拿掉80的点……后来经过【地表最强】lsy的山寨数据测了一下,发现还是稳稳的40分……
暴力以及考场心路历程就说到这吧,该讲讲正解是啥咧。
嗯……概括一下,就是这题没A就是数列没学好。
我们开一个lzy_rol[n], lzy_lin[m]两个标记数组,记录每一行或是每一列要乘上多少。假设我们没有列的操作,那么一列看成一个数,整张图就是一个长度为m的等差数列,不想O(1)的话O(m)就能求出来。现在加上了列的操作。其实就是对于每一个数ai乘上了一个lzy_lin[i],只要按顺序算到ai,然后答案加上ai * lzy_lin[i]就行了…………

1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a, x) memset(a, x, sizeof(a)) 15 #define rg register 16 typedef long long ll; 17 typedef double db; 18 const int INF = 0x3f3f3f3f; 19 const db eps = 1e-8; 20 const ll mod = 1e9 + 7; 21 const int maxn = 1e6 + 5; 22 inline ll read() 23 { 24 ll ans = 0; 25 char ch = getchar(), last = ' '; 26 while(!isdigit(ch)) {last = ch; ch = getchar();} 27 while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();} 28 if(last == '-') ans = -ans; 29 return ans; 30 } 31 inline void write(ll x) 32 { 33 if(x < 0) x = -x, putchar('-'); 34 if(x >= 10) write(x / 10); 35 putchar(x % 10 + '0'); 36 } 37 38 int n, m, q; 39 char c[5]; 40 ll lzy_rol[maxn], lzy_lin[maxn]; 41 ll s = 0, d = 0, sum = 0; 42 43 ll getnum(int x, int i) 44 { 45 return ((ll)(i - 1) * m + x) % mod; 46 } 47 48 int main() 49 { 50 freopen("game.in", "r", stdin); 51 freopen("game.out", "w", stdout); 52 n = read(); m = read(); q = read(); 53 for(int i = 1; i < maxn; ++i) lzy_rol[i] = lzy_lin[i] = 1; 54 for(int i = 1; i <= q; ++i) 55 { 56 scanf("%s", c); int x = read(); ll k = read(); 57 if(c[0] == 'R') lzy_rol[x] *= k, lzy_rol[x] %= mod; 58 else lzy_lin[x] *= k, lzy_lin[x] %= mod; 59 } 60 for(int i = 1; i <= n; ++i) 61 { 62 s += getnum(1, i) * lzy_rol[i]; s %= mod; 63 d += lzy_rol[i]; d %= mod; 64 } //s:首项,d:公差 65 for(rg int i = 1; i <= m; ++i) 66 { 67 sum += lzy_lin[i] * s; sum %= mod; 68 s += d; s %= mod; 69 } 70 write(sum); enter; 71 return 0; 72 }
T2 jump
在考场上我第一反应就是20分暴力,然后chuachuachua15分钟就写完了。于是就直接去搞第三题的暴力了。
第三题暴力写完后又回来看这道题,然后就想到了一个50分的想法:先求出一个跳房子的周期,然后移动每一次k步,只要对周期取模即可。至于修改,那就是暴利修改啦。然而时间不是很够,于是就没写完。
至于正解,我到现在还不是很懂。学姐写的是线段树维护置换。然后正解是一个很奇特的算法,不过这俩都没听懂……据说路由器还有一个更简单的做法,然而没人跟我讲啊……
T3 sequence
我上文已经说过了,当时也是写的暴利。而且还没想到比较正常的O(n2)暴利,自己搞了一个很奇葩的算法:就是先预处理每一个数出现的位置,然后统计当前查询区间没出现的数,然后利用这些数字不断扩大查询区间,直到区间最大值减最小值等于区间长度为止。
正解其实跟我的暴利有点像:要维护两个线段树:第一个是区间最值,第二个是在值域区间中出现位置的最值(所以说,RMQ其实就行了,而且常数更小)。
然后我们要递归的查询:先在询问区间[L, R]中查询最小最大值Min, Max.然后在第二个线段树的区间[Min, Max]中查询出现位置的最值Minl, Minr,再换到第一个线段树在[Minl, Minr]中查询数的最大最小值……直到Max - Min = R - L,这时候的L, R 就是答案。

1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cctype> 8 #include<vector> 9 #include<stack> 10 #include<queue> 11 using namespace std; 12 #define enter puts("") 13 #define space putchar(' ') 14 #define Mem(a, x) memset(a, x, sizeof(a)) 15 #define rg register 16 typedef long long ll; 17 typedef double db; 18 const int INF = 0x3f3f3f3f; 19 const db eps = 1e-8; 20 const int maxn = 1e5 + 5; 21 inline ll read() 22 { 23 ll ans = 0; 24 char ch = getchar(), last = ' '; 25 while(!isdigit(ch)) {last = ch; ch = getchar();} 26 while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();} 27 if(last == '-') ans = -ans; 28 return ans; 29 } 30 inline void write(ll x) 31 { 32 if(x < 0) x = -x, putchar('-'); 33 if(x >= 10) write(x / 10); 34 putchar(x % 10 + '0'); 35 } 36 37 int n, m, a[maxn]; 38 39 struct Tree 40 { 41 int a[maxn]; 42 int l[maxn << 2], r[maxn << 2], Min[maxn << 2], Max[maxn << 2]; 43 Tree() 44 { 45 Mem(a, 0); 46 Mem(l, 0); Mem(r, 0); 47 Mem(Min, 0); Mem(Max, 0); 48 } 49 void build(int L, int R, int now) 50 { 51 l[now] = L; r[now] = R; 52 if(L == R) {Min[now] = Max[now] = a[L]; return;} 53 int mid = (L + R) >> 1; 54 build(L, mid, now << 1); 55 build(mid + 1, R, now << 1 | 1); 56 Min[now] = min(Min[now << 1], Min[now << 1 | 1]); 57 Max[now] = max(Max[now << 1], Max[now << 1 | 1]); 58 } 59 int query(int L, int R, int now, bool flg) 60 { 61 if(L == l[now] && R == r[now]) return flg ? Max[now] : Min[now]; 62 int mid = (l[now] + r[now]) >> 1; 63 if(R <= mid) return query(L, R, now << 1, flg); 64 else if(L > mid) return query(L, R, now << 1 | 1, flg); 65 else 66 { 67 int ret; 68 if(flg) ret = max(query(L, mid, now << 1, flg), query(mid + 1, R, now << 1 | 1, flg)); 69 else ret = min(query(L, mid, now << 1, flg), query(mid + 1, R, now << 1 | 1, flg)); 70 return ret; 71 } 72 } 73 }Seq, Num; 74 75 #define pr pair<int, int> 76 #define mp make_pair 77 #define fir first 78 #define sec second 79 pr Query(int L, int R) 80 { 81 while(1) 82 { 83 int Ml = Seq.query(L, R, 1, 0), Mr = Seq.query(L, R, 1, 1); 84 if(Mr - Ml == R - L) return mp(L, R); 85 L = Num.query(Ml, Mr, 1, 0); 86 R = Num.query(Ml, Mr, 1, 1); 87 } 88 } 89 90 int main() 91 { 92 freopen("sequence.in", "r", stdin); 93 freopen("sequence.out", "w", stdout); 94 n = read(); 95 for(int i = 1; i <= n; ++i) a[i] = read(); 96 for(int i = 1; i <= n; ++i) Seq.a[i] = a[i], Num.a[a[i]] = i; 97 Seq.build(1, n, 1); Num.build(1, n, 1); 98 m = read(); 99 for(int i = 1; i <= m; ++i) 100 { 101 int L = read(), R = read(); 102 pr p = Query(L, R); 103 write(p.fir); space; write(p.sec); enter; 104 } 105 return 0; 106 }
不过这个线段树好像常数很大,然后换了数据后后面几个点就TLE了……那真正的正解就不太懂了。
总的来说今天的考试还是挺顺利的,暴利一个半点很愉快的就写完了。自己估分100左右,结果出成绩后刚好102,顿时觉得自己相当nb。反正想拿的分都拿到了,就没什么遗憾。