Codeforces Round #701 (Div. 2)
A - Add and Divide
题意
有两个数a和b,每次可以选择把a除去b(下取整)或者把b加1,问最少操作几次使a等于0。
\(a,b\le10^9\)
题解
显然这个操作次数不会很多,因为哪怕a是\(10^9\),b是2,最多操作32次肯定够了。
而且注意到第二个操作肯定放在操作的最前面。
于是暴力枚举第二个操作执行几次(\(<32\))即可。
#include <bits/stdc++.h> #define int long long #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } int ans = 0, a, b; void work() { ans = 0x3f3f3f3f; a = read(); b = read(); int t1, t2, cnt; for(int i = 0; i <= 100; i++) { if(b + i == 1) continue; cnt = 0; t1 = a; t2 = b + i; while(t1 != 0) { t1 /= t2; cnt++; } ans = min(ans, cnt + i); } printf("%lld\n", ans); } signed main() { int Case = read(); while(Case--) work(); return 0; }
B - Replace and Keep Sorted
题意
定义b与a相似当且仅当:
a,b为严格单调递增的等长正数序列,a,b中的每个元素都在\([1,k]\)中,a,b只有一个元素不同。
有q个询问,每次询问与a的一个子段相似的b有多少个。
\(n,q\le 10^5,\)
题解
因为只需要改变一个数,我们判断一下每个数可以变化的范围(大于左边的数小于右边的数)就行了。
但是注意区间两端的数需要特判,因为左端点只要大于0,右端点只要小于k+1即可。
#include <bits/stdc++.h> #define int long long #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } const int N = 2e5 + 1009; int n, q, k, a[N], b[N]; void work() { n = read(); q = read(); k = read(); for(int i = 1; i <= n; i++) a[i] = read(); a[0] = 0; a[n + 1] = k + 1; for(int i = 1; i <= n; i++) b[i] = a[i + 1] - 1 - (a[i - 1] + 1) + 1 - 1 + b[i - 1]; //for(int i = 1; i <= n; i++) // cout << b[i] - b[i - 1] << endl; for(int i = 1; i <= q; i++) { int l = read(), r = read(); //cout << b[r - 1] - b[l] << endl; if(l == r) printf("%lld\n", k - 1); else printf("%lld\n", b[r - 1] - b[l] + a[l + 1] - 1 - 1 + k - a[r - 1] - 1); } } signed main() { int Case = 1; while(Case--) work(); return 0; }
C - Floor and Mod
题意
定义一对数\((a,b)\)是漂亮的,有\(\lfloor\frac{a}{b}\rfloor == a mod b\),求\(1\le a \le x, 1 \le b \le y\)中有多少对\((a,b)\)是漂亮的。
\(a,b\le 10^9\)
题解
我们把式子化简下。
枚举这个b,假设枚举到i。
另\(a = i * k + k(k < i)\),提出k可以得到\(k = \frac{a}{i+1} <b\)
把b除过来得到\(\frac{a}{i^2+i}=0(<1取整)\),且\(i+1|a\)
显然a只能是\([0,i^2+i-1]\)之间的一个数而且a能被i整除,这样的a有\(\frac{min(i^2+i-1, x)}{i}\)个。
暴力枚举这个i可以发现答案是对的,但是会超时,继续观察,发现i很大的时候,分子全是x。
分子相同的时候统计这个和可以用数论分块解决。
因为\(i^2+i-1 <= x\)的i最多有\(\sqrt{x}\)个,时间是\(O(\sqrt{x})\)的。
而大于的情况用数论分块之后也是\(O(\sqrt{x})\)的,所以总时间就是\(O(\sqrt{x})\)。
#include <bits/stdc++.h> #define int long long #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } int x, y, ans; void work() { int l, r, i; ans = 0; x = read(); y = read(); for(i = 1; i * i + i - 1 <= x && i <= y; i++) { int r = i * i + i - 1; ans += r / (i + 1); } //cout << i << endl; l = i + 1; for( ; l <= y + 1 && l <= x; l = r + 1) { r = x / (x / l); if(r > y + 1) break; ans += x / l * (r - l + 1); } if(l <= x) { r = y + 1; ans += x / l * (r - l + 1); }printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">%lld\n</span><span style="color: #800000;">"</span><span style="color: #000000;">, ans);
}
signed main()
{
int Case = read();
while(Case--) work();
return 0;
}
D - Multiples and Power Differences
题意
有一个\(n\times m\)的矩阵\(a\)要求构造一个矩阵\(b\)使得\(b\)中的每个元素都是\(a\)中对应元素的倍数,并且\(b\)矩阵每个元素与上下左右四个元素之间的差的绝对值可以开四次方根。
\(a\)中元素\(1 \le a_{i,j} \le 16\)
题解
(谁能想到C题这么难的数学题之后搞道简单构造题出来)
1到16的最小公倍数是\(720720\),是小于\(10^6\)的,于是每个位置都可以填\(720720\),但是相邻两个数要不一样怎么办。
那就\((i+j-1) mod 2==0\)的位置填\(720720\),下一个位置是\(x\)就减去\(x^4\)就行了。
(还是要吐槽一句居然比C简单不少)
#include <bits/stdc++.h> #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } int n, m, a[509][509]; signed main() { n = read(); m = read(); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read(); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { if((i + j - 1) & 1) printf("720720 "); else printf("%d ", 720720 - a[i][j] * a[i][j] * a[i][j] * a[i][j]); } printf("\n"); } return 0; }
E - Move and Swap
题意
有一棵树,根到所有叶子距离相等。
一开始红和蓝都在1号点,每次红可以选择一个儿子移动下去,蓝可以任意移动到下一层的一个节点。
然后你可以选择交换或者不交换红和蓝的位置。
然后对答案产生\(|a_r - a_b|\)的贡献。
问把红和蓝最终移动到叶子上是总贡献最大是多少。
题解
树形dp。
\(f[x]\)表示红点走到x时的最大贡献。
单独考虑红点的转移:
红点可以来自他的父亲,这时候蓝点只要选择当前层的最大或者最小值就行了。
或者红点向下走一格后和蓝点交换,这个意思相当于蓝点选择一个儿子走下去,红点任意走。
转移到\(f[x] = f[y] + |a_{son_y} - a_x|\)。
前一个转移是\(O(1)\)的,后面能不能也改成\(O(1)\)?
答案是肯定的。
把绝对值拆开 \(|a_{son_y} - a_x| = max(a_{son_y} - a_x, a_x - a_{son_y})\)
这样子\(f[x] = max{f[y] + a_{son_y} - a_x, f[y] - a_{son_y} + a_x}\)
对于当前层的每一个y,统计一个\(f[fa_y] + a_y\)的最大值和\(f[fa_y] - a_y\)的最大值即可。
#include <bits/stdc++.h> #define int long long #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } const int N = 2e5 + 1009; vector<int> son[N], nd[N]; int a[N], n, dth[N], maxx[N], minx[N], maxd, ans, maxa, maxb, f[N], fa[N]; void dfs(int x, int pre) { dth[x] = dth[pre] + 1; maxd = max(maxd, dth[x]); maxx[dth[x]] = max(maxx[dth[x]], a[x]); minx[dth[x]] = min(minx[dth[x]], a[x]); nd[dth[x]].push_back(x); for(auto j : son[x]) dfs(j, x); } void work() { n = read(); maxd = ans = 0; maxa = maxb = -1ll << 61; for(int i = 1; i <= n; i++) son[i].clear(), fa[i] = 0; for(int i = 2; i <= n; i++) { int x = read(); son[x].push_back(i); fa[i] = x; } for(int i = 2; i <= n; i++) a[i] = read(); for(int i = 1; i <= n; i++) nd[i].clear(); for(int i = 1; i <= n; i++) dth[i] = 0, maxx[i] = 0, minx[i] = 1ll << 61, f[i] = 0; dfs(1, 1); for(auto x : nd[2]) maxa = max(maxa, a[x]), maxb = max(maxb, -a[x]); for(int i = 2; i <= maxd; i++) { maxa = -1ll << 61; maxb = -1ll << 61; for(auto x : nd[i]) { maxa = max(maxa, f[fa[x]] + a[x]); maxb = max(maxb, f[fa[x]] - a[x]); } for(auto x : nd[i]) { //从他的父亲直接走下来 f[x] = max(f[x], f[fa[x]] + max(a[x] - minx[dth[x]], maxx[dth[x]] - a[x])); //从上一个地方换下来 f[x] = max(f[x], max(maxa - a[x], maxb + a[x])); ans = max(ans, f[x]); } } printf("%lld\n", ans); } signed main() { int Case = read(); while(Case--) work(); return 0; }
F - Copy or Prefix Sum
题意
有一列数b,要求构造数列a,使得每个\(b_i\),要么\(b_i=a_i\),要么\(b_i = \sum_{k=1}^{k\le i}a_k\),求构造方案数。
题解
看起来就像dp,往dp方面想。
可以构造\(f[i][j]\)是前i个数,前缀和为j的方案数。
假如满足第一种构造:\(f[i][j+b_i] += \sum f[i-1][j]\),其中\(a_i=b_i\)
假如满足第二种构造:\(f[i][b_i] += \sum f[i-1][j]\),其中\(a_i=b_i - \sum_{k=1}^{k\le i - 1}a_k\)
两种\(a_i\)相等当且仅当\(\sum_{k=1}^{k\le i - 1}a_k = 0\)即第一种中的j=0。
要减去这个防止重复计算。
但这样时间复杂度是\(O(n^2)\)的,可以采取方法降低复杂度。
对于第一种操作,可以发现是所有元素像右移动\(b_i\)个单位,可以用一个\(shift\)变量保存移动多少单位,可以\(O(1)\)解决。
对于第二种操作,是\(b_i\)加上了所有元素的和,但当\(j=0\)时重复计算,而在第一种操作的时候只是让\(b_i\)位置变成了\(f[i][0]\),所以直接让第二个位置等于所有元素的和就行了。
然后统计全局和。
发现除了\(b_i\)位置变成上一个全局和外都没变,所以全局和改变了\(sum-b_i\),变成\(2*sum-b_i\)。
这个\(b_i\)可能很大,所以要用map当数组。
#include <bits/stdc++.h> #define int long long #define Mid ((l + r) >> 1) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; const int mod = 1e9 + 7; int read(){ char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; }void work() {
int sum = 0, n;
map<int, int>f;
n = read();
int k = 0, ans = 1; f[0] = 1;
for(int qwq = 1; qwq <= n; qwq++) {
int x = read(), t;
t = f[k]; f[k] = ans; k -= x;
ans = (2 * ans - t + mod) % mod;
}
printf("%lld\n", ans);
}
signed main()
{
int Case = read();
while(Case--) work();
return 0;
}