C.Cheering
题意:
判断一个字符串中\(LSC\), \(PCMS\) 哪个字符穿出现的次数多,如果一样多输出\(Tie\)
思路:
模拟就行
signed main() {
std::string s;
cin >> s;
int res = 0, ret = 0;
std::string A = "LSC", B = "PCMS";
int N = s.size();
for (int i = 0; i <= N - 3; i++) {
if (s.substr(i, 3) == A) res++;
}
for (int i = 0; i <= N - 4; i++) {
if (s.substr(i, 4) == B) ret++;
}
if (res == ret) puts("Tie");
else puts(res > ret ? "LSC" : "PCMS");
return 0;
}
A.Ambiguous Dates
题意:
一共有 \(N\) 月,每一月有 \(D_i\) 天,要求对 \(D_\left[1\dots N\right]\) 重排序,使得最后日和月可以互换的合法天数最少。
思路:
因为月是从小到大递增的,应该尽可能的让第 \(i\) 月的 \(D_i\) 小,这样就可以防止 \(D_i\)月\(i\)日的日期合法。故将 \(D_i\) 从小到大排个序然后统计就好了。
signed main() {
int n; cin >> n;
vector<int> d(n + 1);
rep(i,1,n + 1) cin >> d[i];
sort(d.begin(), d.end());
long long ans = 0;
rep(i,1, n + 1) if (d[i] > i) ans += min<long long>(n - i, d[i] - i);
printf("%lld\n", ans * 2);
return 0;
}
B. Bacteria Experiment
题意:
给一个无向树,每一次可以让 \(u, v\) 相连边(当且仅当 \(u -> x -> v\) ), 求出多少次操作后可以让这棵树变成完全图。
思路:
首先假设这是一条链,现在模拟一下整个的流程
深度为 \(5\) 需要三次操作。由于每一次能够连的点的数量增长的十分快,所以应该去看深度而不是点数的变化,第一次深度为 \(1\) 的点可以连向深度为 \(3\) 的, 第二次深度为 \(1\) 的就可以连向深度为 \(4和5\) 的, 再下一次就可以连向深度为 \(6 7 8 9\) 的点,不难看出每一次都是在上一次的基础上 \(\times 2\) ,所以只需要知道树的直径的最高二进制位是多少。
const int N = 500005;
int h[N], idx;
struct node {
int v, nxt;
}e[N * 2];
void Add(int u, int v) {
e[++idx] = {v, h[u]}, h[u] = idx;
}
int dep[N];
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa) continue;
dfs(v, u);
}
}
signed main() {
int n; cin >> n;
rep(i, 1, n) {
int u, v;
cin >> u >> v;
Add(u, v);
Add(v, u);
}
dfs(1, 0);
int rt1 = 0, mx = 0;
for (int i = 1; i <= n; i++) {
if (dep[i] > mx) mx = dep[i], rt1 = i;
}
memset(dep, 0, sizeof(int) * (n + 1));
dfs(rt1, 0);
mx = *max_element(dep + 1, dep + 1 + n);
mx--;
int ans = 0, p = 1;
while(p < mx) ans++, p *= 2;
printf("%d\n", ans);
return 0;
}
D. Distribution of Days
题意:
求出 \(S \sim E\) 年间,\(M\) 月 \(D\) 日出现在一周七天内分别出现了多少次。
思路:
由于闰年是 \(4\) 年一闰 \(100\) 年不闰,\(400\) 年闰,所以可以将年分成 \(400\) 为一个循环,那么这 \(400\) 年中一共有 \(146097\) 天,而 \(7 \mid 146097\) 所以只需要用 \(400\) 作为一个循环节就可以了,预处理出来 \(2000\) 年到 \(2400\) 年 \(M.D\) 出现在周一到周日的次数就行了。
int day[1000][13][40];
int a[13];
int check(int x) {
if ((x % 400 == 0) || (x % 4 == 0 && x % 100 != 0)) return true;
return false;
}
signed main() {
rep(i, 1, 13) {
if (i == 2) a[i] = 28;
else if (i <= 7) a[i] = 30 + (i & 1);
else if (i >= 8) a[i] = 30 + (i & 1 ^ 1);
}
int idx = 6;
rep(i, 0, 400) {
rep(j, 1, 13) {
if (j == 2) {
for (int k = 1; k <= a[j] + check(i); k++) {
idx %= 7;
day[i][j][k] = ++idx, day[i + 400][j][k] = idx;
}
} else {
for (int k = 1; k <= a[j]; k++) {
idx %= 7;
day[i][j][k] = ++idx, day[i + 400][j][k] = idx;
}
}
}
}
int q; cin >> q;
while(q--) {
int s, e, m, d;
cin >> s >> e >> m >> d;
array<long long, 8> ans{};
if (e - s >= 400) {
int p = (e - s) / 400;
for (int i = s % 400; i <= s % 400 + 399; i++) ans[day[i][m][d]] += p;
}
e %= 400, s %= 400;
if (e < s) e += 400;
for (int i = s; i <= e; i++) ans[day[i][m][d]]++;
rep(i, 1, 8) printf("%lld%c", ans[i], " \n"[i == 7]);
}
return 0;
}
E. Expected Score
题意:
一共有 \(N\) 个数,\(2\times N\) 个箱子,每一个数出现在两个不同箱子上面。每一轮游戏取出两个箱子,如果这两个箱子上面的数字相同,就将这个数字该轮游戏的得分, 否则得分为\(0\),最后将箱子放回原位。现在一共有 \(R\) 轮游戏,已经进行了 \(R - 1\) 轮,求出最后一轮可以得到该轮游戏最高分的期望是多少。
思路:
由于我们可以知道前面 \(R - 1\) 轮取出的箱子他们表示的数是多少,所以我们可以把 \(N\) 个数分成三种情况来讨论,第一种是 \(i\) 在不同位置的箱子上出现了两次,第二种是 \(i\) 在已知的箱子中只有一个表示 \(i\), 第三种是 \(i\) 在已知的箱子中没有出现。将数分成了三类,那么将答案也分三种情况讨论,将 \(R - 1\) 轮最高分计为 \(res\), 第 \(R\) 轮的得分记为 \(ret\), 未知的箱子数量记为 \(M\)
第一种情况:在已知的箱子里面选出两个箱子作为最后的得分,很显然你一定是在 \(res + 1 \sim N\) 中选择两个数,否则答案一定不是最优的,那么这种情况的期望就是 \(ret\)
第二种情况:在已知的箱子中选择一个箱子在未知的箱子中选择一个,那么一定是 \(ret\) 在已知的箱子中只出现了一次,所以在 \(M\) 个箱子中只有一个箱子可能出现 \(ret\), 那么最后我们得到 \(ret\) 的概率是 \(\frac{1}{M}\), 而得到 \(res\) 的概率是 \(\frac{M - 1}{M}\) ,所以这种情况的期望是 \(ret\times \frac{1}{M} + res \times \frac{M - 1}{M}\)
第三种情况:在未知的箱子中选择两个数,选到 \(ret\) 的概率是 \(\frac{1}{M\times \left(M - 1\right)}\) ,选到 \(res\) 的概率是 \(1 - \sum\limits_{i = res + 1}^{N} i \times \frac{1}{M\times \left(M - 1\right)} (i \in unkown)\) 所以最后的期望也就求出了。
最终的答案就是三种情况的期望取最大值
signed main() {
int n, r;
cin >> n >> r;
std::vector<int> cnt(n + 1), used(n * 2 + 1);
int cur = 0;
int M = 0;
rep(i, 1, r) {
int x, y, cx, cy;
cin >> x >> y >> cx >> cy;
if (!used[x]) used[x] = 1, M++, cnt[cx]++;
if (!used[y]) used[y] = 1, M++, cnt[cy]++;
if (cx == cy) cur = max(cur, cx);
}
M = 2 * n - M; // total of unopened boxes
int res = 0;
// case1 : both opened
for (int i = 1; i <= n; i++) {
if (cnt[i] == 2) res = i;
}
res = max(res, cur);
double ret = 0;
std::vector<int> open, unopen;
rep(i, 1, n + 1) {
if (cnt[i] == 1 && cur < i) open.push_back(i);
else if (!cnt[i] && cur < i) unopen.push_back(i);
}
ret = max<double>(ret, res);
// case2 : one open one unopen
for (auto& x : open) ret = max<double>(ret, x * 1.0 / M + cur * (M - 1.0) / M);
//case3 : both unopen
double ans = 0, now = 0;
for (auto& x : unopen) {
ans += 2.0 / M / (M - 1) * x;
now += 2.0 / M / (M - 1);
}
ans += (1.0 - now) * cur;
ret = max<double>(ans, ret);
printf("%.10lf\n", ret);
return 0;
}
F. Frustrating Game
题意:
给 \(N\) 个排成一行的仅由 \(R B\) 组成的球,可以使用魔法将 \(r\)个红球和 \(b\) 个蓝球消掉变成白球,但是这些球中间不能够有白球。
思路:
如果\(r = 1 \&\& b = 1\) 那么就可以将\(RB\) 串看作是一个括号序列,让我们求出该匹配方案,这就只需要用一个栈来作括号序列的匹配就可以了。那么类比到一般的情况,就每次判断在栈顶的 \(r + b\) 的元素中是不是有 \(r\) 个 \(R\) \(b\) 个 \(B\),是的话就删掉这些数,最后将方案倒着输出一遍就行。
int stk[100005], top;
std::string s;
int n, r, b;
int sum[100005][2];
signed main() {
cin >> n >> r >> b;
cin >> s;
int R = 0, B = 0;
for (auto& ch : s) R += (ch == 'R'), B += (ch == 'B');
if (R % r || B % b || (R / r != B / b)) {
puts("NO");
return 0;
}
puts("YES");
vector<vector<int>> ans;
int res = 0, ret = 0;
for (int i = 0; i < static_cast<int>(s.size()); i++) {
res += s[i] == 'B';
ret += s[i] == 'R';
sum[i][0] = res;
sum[i][1] = ret;
stk[++top] = i;
if (top > r + b) {
int p = stk[top - r - b];
if (res - sum[p][0] == b && ret - sum[p][1] == r) {
vector<int> cur;
for (int j = 1; j <= r + b; j++) {
res -= s[stk[top]] == 'B';
ret -= s[stk[top]] == 'R';
cur.push_back(stk[top]);
top--;
}
reverse(cur.begin(), cur.end());
ans.emplace_back(cur);
}
}
}
if (top) {
assert(top == r + b);
vector<int> cur;
for (int i = 1; i <= top; i++) {
cur.push_back(stk[i]);
}
ans.emplace_back(cur);
}
reverse(ans.begin(), ans.end());
printf("%d\n", static_cast<int>(ans.size()));
for (auto& vec : ans) {
for (auto& x : vec) {
printf("%d ", x + 1);
}
puts("");
}
return 0;
}
G. Gravitational Tetris
题意:
有 \(8\) 种积木,且可以自由下落,构造一组方案使得最后, \(10\) 列拥有一样的行数。
思路:
可以将所有前 \(9\) 列的格子都拼到 \(40\) 行,最后一列只有可能是 \(cnt \% 4 = 2\ or\ 0\) 这样才是有解的,前面 \(9\) 列的拼法就是 \(h_i + 4x + 3y = 40\) 且 \(y \leq 3\), 采用 \(A, G\) 两种积木来拼,同时需要去更新 \(a_{i + 1}\), 因为 \(G\) 种积木他向右伸出了一格,最后一列特判一下就行了,如果是 \(cnt \% 4 = 2\) 的情况需要用一个 \(E\) 种积木和两个 \(B\) 来调整所有列变成 \(41\) 行。
int h[11];
int cnt[10];
signed main() {
rep(i, 0, 10) {
cin >> h[i];
}
string s;
vector<int> ret;
int sum = 0;
rep(i, 0, 9) {
int d = 40 - h[i];
int p = d / 4;
int a = 0, g = 0;
for (int j = p; j >= 0; j--) {
if ((40 - h[i] - 4 * j) % 3 == 0) {
a = j, g = (40 - h[i] - 4 * j) / 3;
break;
}
}
h[i + 1] += g;
h[i] = 40;
rep(j, 0, a) s += 'A', ret.push_back(i + 1);
rep(j, 0, g) s += 'G', ret.push_back(i + 1);
sum += a + g;
}
if (h[9] % 4 == 2) {
for (int i = 0; i < (38 - h[9]) / 4; i++) {
sum++;
s += 'A';
ret.push_back(10);
}
s += 'E', ret.push_back(9);
s += 'B', ret.push_back(1), s += 'B', ret.push_back(5);
sum += 3;
} else if (h[9] % 4 == 0) {
for (int i = 0; i < (40 - h[9]) / 4; i++) {
s += 'A';
ret.push_back(10);
sum++;
}
}
printf("%d\n", sum);
for (int i = 0; i < s.size(); i++) {
printf("%c %d\n", s[i], ret[i]);
}
return 0;
}
H. Hit!
题意:
求出两圆任意一个公共点
思路:
计算几何板子,也可以用定比例分点、
using namespace Geometry;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::cout << std::fixed << std::setprecision(10);
Circle p1, p2;
cin >> p1.p >> p1.r;
cin >> p2.p >> p2.r;
if (intersect(p1, p2) > 1) { //相交或外切
auto ret = crosspoint(p1, p2);
Point res = ret.first + ret.second;
cout << res.real() / 2 << " " << res.imag() / 2 << "\n";
} else { //内切或者内含
if (p1.r > p2.r) std::cout << p2.p << "\n";
else std::cout << p1.p << "\n";
}
}
I. Inverted Signs
题意:
可以改变 \(\left[l \dots r\right]\) 区间内所有数变成他们的相反数,求出最后整个序列 \(\sum\limits_{i = 1}^{n - 1} \left| h_{i} - h_{i + 1}\right|\) 的最小值。
思路:
因为区间内改变正负性,不会影响这个区间对答案的贡献,所以只需要计算出相邻两项变化值是多少,最后取最小的两个负数加到原来的答案上就可以了
signed main() {
int n; cin >> n;
long long ans = 0;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
vector<long long> res(n);
for (int i = 1; i < n; i++) {
ans += abs(a[i] - a[i - 1]);
}
for (int i = 0; i < n - 1; i++ ) {
res[i] = abs(-a[i + 1] - a[i]) - abs(a[i + 1] - a[i]);
}
sort(res.begin(), res.end());
if (res[0] < 0) ans += res[0];
if (res[1] < 0) ans += res[1];
printf("%lld\n", ans);
return 0;
}
J. Juicy Candies
题意:
有 \(b\) 个 \(B\), \(r\) 个 \(R\), \(s\) 个 \(S\), 且相邻的不能相同,求字典序第 \(k\) 小的方案。
思路:
求出字典序第 \(k\) 小也就是要求出对应的一个前缀,计算这个前缀的方法可以只关心最后一个字符和前面一共有多少个 \(R B S\), 这样我们可以转化成求出求一个后缀,这样就只需要关心这个后缀中有多少个\(RBS\) 和第一个字符是什么。这样就可以用一个 \(dp\) 来转移方案数,定义 \(dp[i][b][s][r]\) 表示现在用了\(b\) 个 \(B\), \(r\) 个 \(R\), \(s\) 个 \(S\), 且在最开头是 \(i \in \{0, 1, 2\}\) 的方案有多少种,转移方程也很容易想到就是从另外两个状态转移过来, 但是如果直接计算方案数的话,可能会爆 \(long\ long\), 我们只需要关心前 \(k\) 个的方案,所以后面的就不需要去关心,所以最后 \(dp[i][b][r][s]\) 都要和 \(k + 1\) 取个最小值
long long dp[210][210][210][3];
signed main() {
int b, r, s;
long long k;
cin >> b >> r >> s >> k;
std::string ans;
dp[1][0][0][0] = dp[0][1][0][1] = dp[0][0][1][2] = 1;
rep(i, 0, b + 1) rep(j, 0, r + 1) rep(p, 0, s + 1) rep(c, 0, 3) {
if (c == 0 && i) dp[i][j][p][c] += dp[i - 1][j][p][1] + dp[i - 1][j][p][2];
if (c == 1 && j) dp[i][j][p][c] += dp[i][j - 1][p][2] + dp[i][j - 1][p][0];
if (c == 2 && p) dp[i][j][p][c] += dp[i][j][p - 1][1] + dp[i][j][p - 1][0];
dp[i][j][p][c] = min(dp[i][j][p][c], k + 1);
}
long long sum = 0;
for (int i = 0; i < 3; i++) sum += dp[b][r][s][i];
if (sum < k) { puts("None"); return 0; }
int N = b + r + s;
int pre = -1;
for (int i = 1; i <= N; i++) {
for (int j = 0; j < 3; j++) if (pre ^ j) {
if (dp[b][r][s][j] >= k) {
if (b && j == 0) b--, ans += 'B';
if (r && j == 1) r--, ans += 'R';
if (s && j == 2) s--, ans += 'S';
pre = j;
break;
} else {
k -= dp[b][r][s][j];
}
}
}
puts(ans.c_str());
return 0;
}
K. Knights
题意:
\(N\times M\)矩形,同行或同列已被占领的格子中间的所有格子都会被占领,求在给定已占领格子的基础上占领所有格子还至少需要派骑士占领多少个格子
思路:
判断四个角就行,另外特判 \(1\times 1\), \(N\times 1\), \(1\times M\) 的情况
signed main() {
int n, m, k;
cin >> n >> m >> k;
int ans = 4;
std::set<std::pair<int, int>> S;
rep(i, 0, k) {
int x, y;
cin >> x >> y;
S.insert(make_pair(x, y));
}
if (n == m && n == 1) {
ans = 1;
ans -= S.count(make_pair(1, 1));
} else if (n == 1 || m == 1) {
ans = 2;
ans -= S.count(make_pair(1, 1));
ans -= S.count(make_pair(n, m));
} else {
ans -= S.count(make_pair(1, 1));
ans -= S.count(make_pair(n, 1));
ans -= S.count(make_pair(1, m));
ans -= S.count(make_pair(n, m));
}
printf("%d\n", max(0, ans));
return 0;
}
L. Let Me Count The Ways
题意:
给定两行,分别 \(n\) 列,\(m\) 列 \(\left(n \geq m\right)\), 将\(1\sim n + m\)个数放入这 $n + m $个格子,使得数字不重复、每行前面的数比后面的数大,每列第一行的数比第二行的数大,求有几种放法。对 \(10^9 + 7\) 取模
思路:
是一个标准杨表的问题,答案就是卡特兰数 \(\binom{n + m}{n} - \binom{n + m}{n + 1}\) 证明参考:L题解
但是这个数太大了 \(N, M \leq 10^9\) 肯定是不可以直接算出所有的 $1\sim 10^9 $ 阶乘和逆元的,所以考虑\(Lucas\) 定理,确实可以解决这个问题,但是在第五个样例的测试的时候要跑 \(22s\) 显然不能够通过本题,那么就要使用分段打表的技巧,预处理出来 \(1000000!, 2000000!, \dots, 10^9!\), 这样算任意一个数的阶乘的时候最多只需要算 \(1000000\) 次就可以算出来了,而且大于 \(10^9 + 7\) 的数的阶乘不需要去计算答案肯定是 \(0\)
const int B = 1e6;
Mint fac[B] = {...}; //预处理出来的阶乘
Mint calc(long long x) {
Mint res = fac[x / B];
for (int i = x / B * B + 1; i <= x; i++) res *= i;
return res;
}
signed main() {
long long n, m; cin >> n >> m;
if (n + m >= P) return printf("0\n"), 0;
else {
Mint a = calc(n + m);
Mint b = calc(m - 1), c = calc(n + 1), d = calc(m), e = calc(n);
printf("%d\n", (a / d / e - a / b / c)());
}
return 0;
}
标签:Salle,Pui,La,int,res,cin,ret,ans,dp
From: https://www.cnblogs.com/Haven-/p/17053368.html