今天的题自认为比Day1简单一些,而且出的质量很高(虽然数据有点水)。
T1 a
期望得分:30
实际得分:30
首先不得不吐槽一下:这题名字起的真糊弄。
说下题吧,一拿到题发现O(n2)暴力就是送的,枚举起始点,再O(n)扫一遍。15分钟敲完后开始打表想想正解,发现对于一个数s[i],他对旋转k次的贡献有一部分是一个等差数列,而有一部分不是,准确说应该是分别有一个递增和递减,公差是1和-1的等差数列 ,以及单出来的一个1。于是我就想找规律,找了30多分钟硬是没找出来,不得不放弃写T2.
T2,T3写完后又会来杠T1,总共用时1小时20分钟,还是只有暴力分……
正解其实和我的思路比较像,但是他找到了等差数列的规律:s[i]对每一个答案的贡献可以分成三个等差数列,然后这道题就变成了区间加一个等差数列,最后求每一个数修改后的值。可以用线段树实现,不过更有的是二阶差分(详见这篇博客),总复杂度O(n)。
但是规律比较复杂,没写出来。
于是我们看第二种方法吧。
强的过分的Dukelv用了不到40分钟AC了这道题,令人震惊。他跟我们说:就是暴力模拟啊!于是就有了一个O(n)暴力模拟的方法。
对于每一次旋转,我们只用考虑旋转后相对于旋转前的答案变了什么,然后不断的维护当前的答案。
因此我们需要一个big:记录当前有几个数s[i] >= i,以及sma:有几个数s[i] < i。然后每一次旋转,先考虑第一个数,因为他的移动方式和其他的都不一样:移到了最后,所以首先是big--, sma++,对答案的贡献少了s[i] - 1,多了n - s[i]。然后考虑剩下的数:有一些的答案由正变负,有一些有负变正。因此我们要开一个数组num[]记录当前有哪些数是负的(当然记录正的也行,反正一个就够了),num[x]表示 s[i] - i = -x的数有多少个,那也就是说,在第k次旋转后,num[k]的这些数就会有负变正,这样对于第 i 次旋转,big += num[i], sma -= num[i],于是对于big和sma的维护也完成了。
有趣的是,标程的代码竟然和Dukelv一样,反而题解的做法没有代码。
先发个30分吧
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 = 2e6 + 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 void MYFILE() 37 { 38 #ifndef mrclr 39 freopen("a.in", "r", stdin); 40 freopen("a.out", "w", stdout); 41 #endif 42 } 43 44 int n, a[maxn]; 45 46 ll ans = INF; 47 48 int main() 49 { 50 MYFILE(); 51 n = read(); 52 for(rg int i = 1; i <= n; ++i) a[i] = read(); 53 for(rg int i = 1; i <= n; ++i) 54 { 55 ll Min = 0; 56 for(rg int j = i, k = 1; k <= n; ++k, ++j) 57 { 58 if(j > n) j -= n; 59 Min += abs(a[j] - k); 60 if(Min > ans) break; 61 } 62 ans = min(ans, Min); 63 } 64 write(ans); enter; 65 return 0; 66 }
满分的
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 = 2e6 + 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 void MYFILE() 37 { 38 39 #ifndef mrclr 40 freopen("a.in", "r", stdin); 41 freopen("a.out", "w", stdout); 42 #endif 43 } 44 45 int n, a[maxn]; 46 int big = 0, sma = 0, num[maxn << 1]; 47 ll ans, Min = 0; 48 49 int main() 50 { 51 MYFILE(); 52 n = read(); 53 for(int i = 1; i <= n; ++i) 54 { 55 a[i] = read(); 56 int x = a[i] - i; 57 if(x >= 0) ++big, Min += x; 58 else ++sma, Min -= x, num[-x]++; 59 } 60 ans = Min; 61 for(int i = 1; i <= n; ++i) 62 { 63 int x = a[i] - 1, y = n - a[i]; 64 big--; Min -= x; 65 sma++; Min += y; num[i + y]++; 66 Min += big - sma + 1; 67 if(num[i]) big += num[i], sma -= num[i]; 68 ans = min(ans, Min); 69 } 70 write(ans); enter; 71 return 0; 72 }
(都好短)
T2 maze
期望得分:30
实际得分:10
还是一眼秒30分,被卡掉20分是因为起点可能不合法,直接输出-1……出题人毒瘤的过分。
正解我是无论如何也想不到的。看n特别小,觉得肯定要在这上面做文章,觉得可能是插头dp什么的,但最终还是没想出来。
正解是咋回事儿呢:n这么小,那么我们就把他看成一个长度为m的序列吧!修改,查询用线段树维护就好啦~
令人震惊。
具体来说,就是对于一个区间[L, R],维护位于第L列所有的点到第R列所有的点的最短距离,也就是说,这个线段树上的每一个点是一个邻接矩阵,t[now].dis[i][j]表示a[i][L]到a[j][R]的最短距离!
考虑修改:就是单点修改,然后暴力重构涉及到的每一个结点的矩阵。区间合并的时候,我们枚举左右子区间挨着的点,在枚举左子区间的起点和右子区间的终点,像floyd构造。
查询:考虑起点和终点所在列不在同一区间的情况:其实和区间合并一样,floyd跑一遍,返回结构体。
于是 写完了。感觉要是想到这个做法的话,这题就挺水的……
30分bfs暴力
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 maxm = 2e5 + 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 void MYFILE() 37 { 38 freopen("maze.in", "r", stdin); 39 #ifndef mrclr 40 freopen("maze.out", "w", stdout); 41 #endif 42 } 43 44 int n, m, q; 45 bool a[6][maxm], vis[6][maxm]; 46 int sx, sy, ex, ey; 47 const int dx[4] = {-1, 0, 1}, dy[4] = {0, 1, 0}; 48 struct Node 49 { 50 int x, y, dis; 51 }; 52 void bfs() 53 { 54 if(!a[sx][sy] || !a[ex][ey] || ey < sy) {write(-1), enter; return;} 55 for(int i = 1; i <= n; ++i) 56 for(int j = sy - 1; j <= ey + 1; ++j) vis[i][j] = 0; 57 queue<Node> q; 58 q.push((Node){sx, sy, 0}); 59 vis[sx][sy] = 1; 60 while(!q.empty()) 61 { 62 Node now = q.front(); q.pop(); 63 if(now.x == ex && now.y == ey) {write(now.dis), enter; return;} 64 for(int i = 0; i < 3; ++i) 65 { 66 int newx = now.x + dx[i], newy = now.y + dy[i]; 67 if(newx > 0 && newx <= n && newy > 0 && newy <= m && a[newx][newy] && !vis[newx][newy]) 68 { 69 vis[newx][newy] = 1; 70 q.push((Node){newx, newy, now.dis + 1}); 71 } 72 } 73 } 74 write(-1), enter; 75 } 76 77 int main() 78 { 79 MYFILE(); 80 n = read(); m = read(); q = read(); 81 for(int i = 1; i <= n; ++i) 82 for(int j = 1; j <= m; ++j) a[i][j] = (bool)read(); 83 for(int i = 1; i <= q; ++i) 84 { 85 int op = read(); 86 if(op == 1) 87 { 88 int x = read(), y = read(); 89 a[x][y] ^= 1; 90 } 91 else 92 { 93 sx = read(), sy = read(), ex = read(), ey = read(); 94 bfs(); 95 } 96 } 97 return 0; 98 }
正解
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 maxm = 2e5 + 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 void MYFILE() 37 { 38 #ifndef mrclr 39 freopen("maze.in", "r", stdin); 40 freopen("maze.out", "w", stdout); 41 #endif 42 } 43 44 int n, m, q, a[6][maxm]; 45 46 struct Tree 47 { 48 int l, r; 49 int dis[6][6]; 50 Tree operator + (const Tree &oth)const 51 { 52 Tree ret; 53 Mem(ret.dis, 0x3f); 54 ret.l = l; ret.r = oth.r; 55 for(int k = 1; k <= n; ++k) 56 for(int i = 1; i <= n; ++i) 57 for(int j = 1; j <= n; ++j) 58 ret.dis[i][j] = min(ret.dis[i][j], dis[i][k] + 1 + oth.dis[k][j]); 59 return ret; 60 } 61 }t[maxm << 2]; 62 void build(int L, int R, int now) 63 { 64 t[now].l = L; t[now].r = R; 65 if(L == R) 66 { 67 Mem(t[now].dis, 0x3f); 68 for(int i = 1; i <= n; ++i) 69 for(int j = i; j <= n; ++j) 70 if(a[j][L]) t[now].dis[i][j] = t[now].dis[j][i] = j - i; 71 else break; 72 return; 73 } 74 int mid = (L + R) >> 1; 75 build(L, mid, now << 1); 76 build(mid + 1, R, now << 1 | 1); 77 t[now] = t[now << 1] + t[now << 1 | 1]; 78 } 79 void update(int now, int d) 80 { 81 if(t[now].l == t[now].r) 82 { 83 Mem(t[now].dis, 0x3f); 84 for(int i = 1; i <= n; ++i) 85 for(int j = i; j <= n; ++j) 86 if(a[j][t[now].l]) t[now].dis[i][j] = t[now].dis[j][i] = j - i; 87 else break; 88 return; 89 } 90 int mid = (t[now].l + t[now].r) >> 1; 91 if(d <= mid) update(now << 1, d); 92 else update(now << 1 | 1, d); 93 t[now] = t[now << 1] + t[now << 1 | 1]; 94 } 95 Tree query(int L, int R, int now) 96 { 97 if(L == t[now].l && R == t[now].r) return t[now]; 98 int mid = (t[now].l + t[now].r) >> 1; 99 if(R <= mid) return query(L, R, now << 1); 100 else if(L > mid) return query(L, R, now << 1 | 1); 101 else return query(L, mid, now << 1) + query(mid + 1, R, now << 1 | 1); 102 } 103 104 int main() 105 { 106 MYFILE(); 107 n = read(); m = read(); q = read(); 108 for(int i = 1; i <= n; ++i) 109 for(int j = 1; j <= m; ++j) a[i][j] = read(); 110 build(1, m, 1); 111 for(int i = 1; i <= q; ++i) 112 { 113 int op = read(); 114 if(op == 1) 115 { 116 int x = read(), y = read(); 117 a[x][y] ^= 1; update(1, y); 118 } 119 else 120 { 121 int sx = read(), sy = read(), ex = read(), ey = read(); 122 int ans = query(sy, ey, 1).dis[sx][ex]; 123 write(ans == INF ? -1 : ans); enter; 124 } 125 } 126 return 0; 127 }
T3 point
期望得分:80~100
实际得分:100
这题我AC了!哈哈哈哈…………
没错,我的方法就是正解。【得瑟】
其实看到这题,我还是先想暴力:首先枚举区间长度len,然后枚举长度为len的所有区间,再对于每一个区间判断是否有价值:首先找区间最小值Min,然后找区间gcd,如果 Min | gcd (其实就是Min == gcd)的话就说明这个区间符合。加上求gcd的复杂度,O(n3logn)。
然后我就想到,区间最小值和gcd可以用线段树实现,不用再O(nlogn)跑一遍,成功降到O(n2log2n)。
然后想到st表预处理查询Min是O(1)的,gcd是O(logn)的,用st表吧,复杂度变成了O(n2logn)。
最后想到枚举区间长度可以从大到小枚举,第一个符合的区间长度一定就是答案,但这样的话复杂度最坏还是能达到O(n2logn)。于是还是二分吧,稳定的O(logn)。于是最终的复杂度就是O(nlog2n)。
总结一下:st表预处理区间最小值MIn和Gcd,复杂度O(nlog2n)。然后二分长度,O(nlogn)判断,复杂度还是O(nlog2n)。
判断的时候加上break,最后再统计区间个数,把优化做到极致。
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 = 5e5 + 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 void MYFILE() 37 { 38 #ifndef mrclr 39 freopen("point.in", "r", stdin); 40 freopen("point.out", "w", stdout); 41 #endif 42 } 43 44 int n; 45 46 inline int gcd(int x, int y) 47 { 48 return y ? gcd(y, x % y) : x; 49 } 50 int dp[maxn][21][2], ha[maxn]; 51 void rmq() 52 { 53 for(rg int i = 1; i <= n; ++i) dp[i][0][0] = dp[i][0][1] = read(); 54 for(rg int j = 1; (1 << j) <= n; ++j) 55 for(rg int i = 1; i + (1 << j) - 1 <= n; ++i) 56 { 57 dp[i][j][0] = min(dp[i][j - 1][0], dp[i + (1 << (j - 1))][j - 1][0]); 58 dp[i][j][1] = gcd(dp[i][j - 1][1], dp[i + (1 << (j - 1))][j - 1][1]); 59 } 60 int x = 0; 61 for(rg int i = 1; i <= n; ++i) 62 { 63 ha[i] = x; 64 if((1 << (x + 1)) <= i + 1) x++; 65 } 66 } 67 inline int query_Min(const int &L, const int &R) //卡常…… 68 { 69 int k = ha[R - L + 1]; 70 return min(dp[L][k][0], dp[R - (1 << k) + 1][k][0]); 71 } 72 inline int query_Gcd(const int &L, const int &R) 73 { 74 int k = ha[R - L + 1]; 75 return gcd(dp[L][k][1], dp[R - (1 << k) + 1][k][1]); 76 } 77 78 bool judge(const int &len) 79 { 80 for(rg int i = 1; i + len - 1 <= n; ++i) 81 { 82 int L = i, R = i + len - 1; 83 int Min = query_Min(L, R), Gcd = query_Gcd(L, R); 84 if(!(Gcd % Min)) return true; 85 } 86 return false; 87 } 88 89 int num = 0; 90 vector<int> ans; 91 92 int main() 93 { 94 MYFILE(); 95 n = read(); 96 rmq(); 97 int L = 0, R = n; 98 while(L < R) 99 { 100 int mid = (L + R + 1) >> 1; 101 if(judge(mid)) L = mid; 102 else R = mid - 1; 103 } 104 for(rg int i = 1; i + L - 1 <= n; ++i) 105 { 106 int l = i, r = i + L - 1; 107 int Min = query_Min(l, r), Gcd = query_Gcd(l, r); 108 if(!(Gcd % Min)) num++, ans.push_back(i); 109 } 110 write(num); space; write(L - 1); enter; 111 for(rg int i = 0; i < (int)ans.size(); ++i) write(ans[i]), space; 112 enter; 113 return 0; 114 }
还特意写了个O(n3logn)对拍了一下,拍完4000组数据后安心了。
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 = 5e5 + 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 void MYFILE() 37 { 38 freopen("random.in", "r", stdin); 39 freopen("bf.out", "w", stdout); 40 } 41 42 int gcd(int x, int y) 43 { 44 return y ? gcd(y, x % y) : x; 45 } 46 47 int n, a[maxn]; 48 int num = 0, val; 49 vector<int> ans; 50 51 int main() 52 { 53 MYFILE(); 54 n = read(); 55 for(int i = 1; i <= n; ++i) a[i] = read(); 56 for(int len = n; len >= 0; --len) 57 { 58 bool flg = 0; 59 for(int i = 1; i + len - 1 <= n; ++i) 60 { 61 int L = i, R = i + len - 1; 62 int Min = INF; 63 for(int j = L; j <= R; ++j) Min = min(Min, a[j]); 64 int Gcd = Min; 65 for(int j = L; j <= R; ++j) Gcd = gcd(Gcd, a[j]); 66 if(!(Gcd % Min)) 67 { 68 if(!flg) {ans.clear(); num = 0; val = len;} 69 flg = 1; num++; 70 ans.push_back(L); 71 } 72 } 73 if(flg) break; 74 } 75 printf("%d %d ", num, val - 1); 76 for(int i = 0; i < (int)ans.size(); ++i) write(ans[i]), space; 77 enter; 78 return 0; 79 }
其实这次模拟挺闲的,T3 40多分钟就敲完了,剩下的时间T1,T2也没抠出来,硬是坐了一个多点……