Codeforces Round #833 (Div. 2)
A. The Ultimate Square (CF 1748 A)
题目大意
给定\(n\)块板,第\(i\)块板的宽为 \(1\),长为 \(\lceil \frac{i}{2}\rceil\)。问用这些板可以组成一个正方形,问其最大的边长是多少。
解题思路
题意可以相当于给定了长度为\(1-\frac{n}{2}\)板各两个。
当 \(n\)是奇数时,第一块板和第 \(n-1\)块板组合,第二块板和第\(n-2\)块板组合,刚好组成\(\frac{n-1}{2}\) 块长度为\(\lceil \frac{n}{2} \rceil\)板,加上第 \(n\)块板刚好可以组成长度为 \(\lceil \frac{n}{2} \rceil\)的正方形
当\(n\)是偶数时直接前后组合,也同样可以组成长度为\(\frac{n}{2}\)的正方形。
因此答案就是 \(\lceil \frac{n}{2} \rceil\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int x;
cin >> x;
cout << (x + 1) / 2 << '\n';
}
return 0;
}
B. Diverse Substrings (CF 1748 B)
题目大意
给定一个仅包含数字的字符串,问其有多少个子串,记\(cnt_i\)表示数字 \(i\)的出现次数, \(up\)为不同数字的个数,满足所有数字 \(i\)均有 \(cnt_i \leq up\)
解题思路
注意数字种类只有\(10\)个,因此合法串的长度最长也就 \(100\),因此对长度为 \(1~100\)的所有子串暴力 判断即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
string s;
cin >> s;
LL ans = 0;
for(int i = 0; i < s.size(); ++ i){
map<int, int> qwq;
set<int> ff;
auto check = [&](){
for(auto &i : qwq)
if (i.second > ff.size())
return false;
return true;
};
for(int j = 1; j <= 100; ++ j){
int r = i + j - 1;
if (r >= s.size())
break;
ff.insert(s[r]);
qwq[s[r]] ++;
if (check())
++ ans;
}
}
cout << ans << '\n';
}
return 0;
}
C. Zero-Sum Prefixes (CF 1748 C)
题目大意
给定一个长度为\(n\)的整数组 \(a\),可以将其中为\(0\)的项变为任意数字,问其前缀和为\(0\)的最大数量是多少,即最大化满足以下条件的\(k\)的数量,使得 \(\sum_{i=0}^{k} a_i = 0\)。
解题思路
即前缀和\(sum_k = \sum_{i=0}^{k} a_i\)
从左到右考虑每个\(0\),假设当前考虑的下标是 \(i\),其值为\(0\), 下一个\(0\)的下标为\(j\)。考虑对前者的\(0\)更改其值,将影响\(sum_{i, ..., j-1}\)的值,即这些值中出现次数最多的是 \(val\),很显然我们将 \(0\)变成 \(-val\),这样就尽可能有更多的前缀和为 \(0\),同时也不影响 \(sum_{j...n}\)(该相同的还是相同)。
因此统计每两个\(0\)相邻之间前缀和出现最多的次数即可。注意别忘了第一个\(0\)出现前前缀和为\(0\)的数量。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> a(n);
for(auto &i : a)
cin >> i;
bool zero = false;
map<LL, LL> cnt;
LL ans = 0;
LL sum = 0;
for(int i = 0; i < n; ++ i){
sum += a[i];
if (a[i] == 0 && zero){
LL id = 0, val = 0;
for(auto &i : cnt){
if (val < i.second){
val = i.second;
id = i.first;
}
}
sum -= id;
ans += val;
cnt.clear();
}else if (a[i] == 0){
zero = true;
cnt.clear();
}else if (!zero && sum == 0)
++ ans;
cnt[sum] ++;
}
if (zero){
LL id = 0, val = 0;
for(auto &i : cnt){
if (val < i.second){
val = i.second;
id = i.first;
}
}
sum -= id;
ans += val;
}
cout << ans << '\n';
}
return 0;
}
D. ConstructOR (CF 1748 D)
题目大意
给定\(a,b,d\),要求给一个 \(x < 2^{60}\),满足
- \(a|x\)被 \(d\)整除
- \(b|x\)被 \(d\)整除
其中\(|\)为二进制或运算
其中\(a, b, d < 2^{30}\)
解题思路
构造题,尽量从简入手。
不妨假设\(a|x = b|x = x\)。由于\(x < 2^{60}\),而 \(a | b < 2^{30}\),我们考虑高\(30\) 位,可以列出以下方程
如果\(d\)是偶数, \(2\)是不存在逆元的,如果此时 \((a|b)\)是奇数,那么该方程无解。但如果是偶数,我们可以所有数提一个 \(2\)出来,此时方程的成立条件并不会变化,可以继续相同判断。
假设提了 \(c\)个 \(2\),方程就变为
此时必有一个数是奇数,如果\((a|b)\)是奇数则无解,如果 \(d\)是奇数,则可解出\(t = inv(2)^{30-c}-(a|b)\)
继而得到 \(x\)。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL qpower(LL a, LL b, LL mo){
LL qwq = 1;
while(b){
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) { x = 1; y = 0; return a; }
LL ret = exgcd(b, a % b, y, x);
y -= a / b * x;
return ret;
}
LL get_inv(LL a, LL M) {
static LL x, y;
assert(exgcd(a, M, x, y) == 1);
return (x % M + M) % M;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
LL a, b, d;
cin >> a >> b >> d;
bool ok = true;
int sign = 0;
while(!(d & 1)){
if ((a & 1) || (b & 1)){
ok = false;
break;
}
d >>= 1;
a >>= 1;
b >>= 1;
++ sign;
}
if (!ok)
cout << "-1" << '\n';
else{
LL ab = (a | b);
LL m = d - ab % d;
LL x = m * qpower(get_inv(2, d), 30 - sign, d) % d;
x <<= 30;
x += (ab << sign);
cout << x << '\n';
}
}
return 0;
}
E. Yet Another Array Counting Problem (CF 1748 E)
题目大意
给定一个整数序列\(a\),记f(l,r)表示子序列 \(a[l..r]\)中值最大且下标最小的下标。
问有多少个整数序列 \(b\),其 \(f(l,r)\)与 \(a\)相同。其中 \(1 \leq b_i \leq m\)
解题思路
又是最值又是下标,容易想到笛卡尔树,实际上就是问有多少个序列\(b\)的笛卡尔树和序列 \(a\)一样。
设\(dp[i][j]\)表示下标为\(i\)取值为\(j\)的方案数,在笛卡尔树上直接\(dp\)即可。
\(dp[i][j] = \sum_{k=1}^{j - 1}dp[lson[i]][k] \times \sum_{l=1}^{j}dp[rson[i]][l]\)
转移的时候记录下前缀和就好了。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
const int N = 1e6 + 8;
const LL mo = 1e9 + 7;
int n, rt, m;
LL val[N], ans;
LL G[N][2];
void build() {
static int s[N], last;
int p = 0;
FOR (x, 1, n + 1) {
last = 0;
while (p && val[s[p - 1]] < val[x]) last = s[--p];
if (p) G[s[p - 1]][1] = x;
if (last) G[x][0] = last;
s[p++] = x;
}
rt = s[0];
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
cin >> n >> m;
for(int i = 1; i <= n; ++ i)
cin >> val[i];
build();
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
function<void(int)> dfs = [&](int u){
if (G[u][0] == 0 && G[u][1] == 0){
fill(dp[u].begin(), dp[u].end(), 1);
return;
}
LL lsum = 0, rsum = 0;
LL lson = G[u][0];
if (lson != 0)
dfs(lson);
LL rson = G[u][1];
if (rson != 0)
dfs(rson);
for(int i = 1; i <= m; ++ i){
rsum += dp[rson][i];
rsum %= mo;
dp[u][i] = (lson == 0 ? 1 : lsum) * (rson == 0 ? 1 : rsum) % mo;
lsum += dp[lson][i];
lsum %= mo;
}
};
dfs(rt);
ans = 0;
for(int i = 1; i <= m; ++ i)
ans = (ans + dp[rt][i]) % mo;
cout << ans << '\n';
for(int i = 0; i <= n; ++ i)
G[i][0] = G[i][1] = 0;
}
return 0;
}