A. From Hero to Zero
通过取余快速运行第一步即可。由于\(a \% b (a >= b) <= \frac{a}{2}\)。所以总复杂度不超过\(O(log_2n)\)。
#include <cstdio> #include <iostream> using namespace std; typedef long long LL; int main(){ int T; scanf("%d", &T); while(T--){ LL n, k, ans = 0; scanf("%lld%lld", &n, &k); while(n){ if(n % k) ans += n % k, n -= n % k; if(n)n /= k, ans++; } printf("%lld\n", ans); } return 0; }
B. Catch Overflow!
循环本质其实是栈的思想,可以用\(loop\)表示这层要循环多少次。注意如果直接乘可能爆\(long\ long\)。
其实当\(loop > 2 ^ {32} - 1\),后面的只要有\(add\)都不行了,所以只要用个\(flag\)记录就行了...
#include <cstdio> #include <iostream> #include <string> #include <stack> using namespace std; typedef long long LL; const LL INF = (1ll << 32) - 1; LL n = 0, loop = 1; stack<int> s; string ch; int x, flag = 0; int main(){ ios::sync_with_stdio(false); int T; cin >> T; while(T--){ cin >> ch; if(ch == "add"){ if(loop > INF){ cout << "OVERFLOW!!!" << endl; return 0; } n += loop; }else if(ch == "for"){ cin >> x; if(loop > INF) { flag++; continue; } s.push(x); loop *= x; }else if(ch == "end"){ if(flag) { flag --; continue; } loop /= s.top(); s.pop(); } if(n > INF){ cout << "OVERFLOW!!!" << endl; return 0; } } cout << n << endl; return 0; }
C. Electrification
注意数据是单调递增的,所以容易想到最短的一段必然是连续的,因为通过绝对值变成正的值顺序一定是\(1, 2, 3....n\),所以每次变成正的后,它可能是最小的,也有可能大于后面的一些,因为减的多了,所以整个区间会往后移动。我们可以尝试枚举每一个\([i, i + k]\)的这个区间,发现能使最小的即变为\((a[i + k] - a[i]) / 2\),也就是全部都减\((a[i + k] - a[i]) / 2\)(可以向下取整)。可以用小根堆维护最小值。由于精度问题,第一个可以不用\(/2\)。
#include <cstdio> #include <iostream> #include <cmath> #include <queue> #include <vector> using namespace std; typedef pair<int, int> PII; const int N = 200010; int n, k, a[N]; priority_queue<PII, vector<PII>, greater<PII> > q; int main(){ int T; scanf("%d", &T); while(T--){ while(q.size()) q.pop(); scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++) scanf("%d", a + i); for(int i = 1; i + k <= n; i++) q.push(make_pair(a[i + k] - a[i], (a[i] + a[i + k]) / 2)); printf("%d\n", q.top().second); } return 0; }
D. Array Splitting
非常巧妙的做法,观察到让我们求的值其实是每一段的和$ * $ 相应的段数,但其实可以发现,一个\(ans\)的组成为:
\(1 * A + 2 * B + 3 * C = (A + B + C) + (B + C) + C\)。实质上就是找到\(k\)个后缀和,将他们加起来求最大值。这样直接贪心求解即可,记得\([1, n]\)这个区间必须选。
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 300010; typedef long long LL; int n, k, a[N]; LL ans, sum[N]; int main(){ scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++) scanf("%d", a + i); for(int i = n; i; i--) sum[i] = sum[i + 1] + a[i]; sort(sum + 2, sum + 1 + n); ans = sum[1]; for(int i = n; i >= n - k + 2; i--) ans += sum[i]; printf("%lld\n", ans); return 0; }
E. Minimal Segment Cover
注意到\(l\)和\(r\)的范围并不大,我们可以预处理出从\(l\)出发最远能到哪里(一条线段),注意\(l\)也可以是中间点。这样我们只需要逐个枚举即可。时间复杂度\(SIZE ^ 2\),可以用倍增的形式优化。具体方式很像\(LCA\),预处理每个\(l\)跳\(2 ^ p(0 <= p <= \lfloor log_2500000 \rfloor)\)能到哪里即可。本质上就是枚举答案的二进制位即可。
时间复杂度\(O(slog_2s)\)(\(s\)是数字范围)
注意预处理顺序,要么\(i\)倒序枚举,要么\(j\)在第一个条件,否则没有正确的转移。
#include <cstdio> #include <iostream> #include <cmath> using namespace std; const int N = 200010, SIZE = 500010, L = 19; int n, m, maxS = -1, f[SIZE][L]; int main(){ scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++){ int l, r; scanf("%d%d", &l, &r); f[l][0] = max(f[l][0], r); maxS = max(maxS, r); } for(int i = 1; i <= maxS; i++) f[i][0] = max(f[i][0], f[i - 1][0]); for(int j = 1; j < L; j++) for(int i = 0; i <= maxS; i++) f[i][j] = f[f[i][j - 1]][j - 1]; for(int i = 1; i <= m; i++){ int x, y, ans = 0; scanf("%d%d", &x, &y); for(int i = L - 1; ~i; i--) if(f[x][i] < y) x = f[x][i], ans |= 1 << i; if(f[x][0] >= y) printf("%d\n", ans + 1); else puts("-1"); } return 0; }