C. Not Covered Points
一眼二维数点,实际上并不需要
我们将每个点按 \(x\) 升序排序后,只需考察 \(y\) 。对于当前的点 \(i\),如果它左边的点的 \(y\) 的最小值大于 \(y_i\) ,就说明点 \(i\) 的左下方没有其他点
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<int> y(n);
rep(i, n) {
int nx, ny;
cin >> nx >> ny;
--nx;
y[nx] = ny;
}
int ymin = n+1;
int ans = 0;
rep(x, n) {
if (ymin > y[x]) ans++;
ymin = min(ymin, y[x]);
}
cout << ans << '\n';
return 0;
}
D. Accomplice
对每个可能的起始时间 \(x\),记 \(c_x\) 为“在区间 \([x,x+D]\) 内始终在场”的人数,那么答案就是对所有整数 \(x\) 求和 \(\sum\limits_x \binom{c_x}{2}\) 。
每个人 \(i\) 对 \(x\) 的贡献是区间 \([S_i,T_i −D]\)(当且仅当 \(T_i−D≥S_i\) 时),因此可以用差分求出所有 \(c_x\) 。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, d;
cin >> n >> d;
const int m = 1e6+5;
vector<int> s(m);
rep(i, n) {
int l, r;
cin >> l >> r;
if (r-l < d) continue;
r -= d-1;
s[l]++; s[r]--;
}
rep(i, m-1) s[i+1] += s[i];
ll ans = 0;
rep(i, m) {
ll x = s[i];
ans += x*(x-1)/2;
}
cout << ans << '\n';
return 0;
}
E. Alternating Costs
假设 \(A < B\)(横着走更便宜)。
如果死磕走直线:
- 奇偶步交替,每两步代价是 \(A + B\)。
- 如果交替改变方向(走斜线):第 \(1\) 步横(代代价 \(A\)),第 \(2\) 步竖(代价 \(A\))。仅用 \(2A\) 的代价,就在横纵向各推进了 \(1\) 格!
设 \(A \le B\) 且 \(X \le Y\)(利用对称性取绝对值):
情况一:\(X+Y\) 为偶数(必走偶数步)
有两种贪心策略相互竞争,取最小值即可:
- 策略 A(全斜线晃动):一路上全部走斜线,纵向走 \(Y\) 步,横向也走 \(Y\) 步(包含折返)。总步数 \(2Y\),每步都是廉价的 \(A\)。
- 策略 B(先斜后直):先用最便宜的斜线切到 \((X,X)\),剩下没得选,只能纯纵向死磕,每两步代价固定为 \(A+B\)。
情况二:\(X+Y\) 为奇数(必走奇数步)
最后临门一脚必然是奇数步(横向走加 \(A\),纵向走加 \(B\))。
直接逆向思考:看它跨入目标点前的 \(4\) 个邻居。这 \(4\) 个邻居的坐标和都是偶数,直接套用上面的“偶数公式”算出最低代价,再加上最后一步的固定成本,\(4\) 者取最小值!
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
ll f(ll a, ll b, ll x, ll y) {
x = abs(x); y = abs(y);
if (a > b) swap(a, b);
if (x > y) swap(x, y);
return min(y*a*2, x*a*2 + (y-x)/2*(a+b));
}
void solve() {
ll a, b, x, y;
cin >> a >> b >> x >> y;
x = abs(x); y = abs(y);
ll ans;
if ((x+y)&1) {
ans = f(a, b, x-1, y) + a;
ans = min(ans, f(a, b, x+1, y) + a);
ans = min(ans, f(a, b, x, y-1) + b);
ans = min(ans, f(a, b, x, y+1) + b);
}
else ans = f(a, b, x, y);
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
F. More ABC
记 dp[i][j][a] 表示到 \(S\) 的第 \(i\) 个字符为止已经增加了 \(j\) 个 ABC,且当前匹配到 ABC 的前 \(a\) 个字符
怎么扣掉“被破坏的原有 ABC”呢?
可以用 std::deque 来维护 \(\text{dp}\)
在处理完每个位置后,如果原字符串当前位置天然存在 ABC,直接弹出 \(dp\) 的队头元素!
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
inline void chmin(int& a, int b) { if (a > b) a = b; }
void solve() {
string s; int k;
cin >> s >> k;
int n = s.size();
const int INF = 1001001001;
deque emp(k+2, vector<int>(3, INF));
auto dp = emp;
dp[0][0] = 0;
rep(i, n) {
auto old = emp;
swap(dp, old);
rep(j, k+2)rep(a, 3) {
for (char c : "ABC?") {
int now = old[j][a];
if (c != '?') now++;
if (c == '?') c = s[i];
int nj = j, na = a;
if ("ABC"[a] == c) na++;
else if (c == 'A') na = 1;
else na = 0;
if (na == 3) na = 0, nj++;
if (nj > k+1) continue;
chmin(dp[nj][na], now);
}
}
if (i >= 2 and s.substr(i-2, 3) == "ABC") {
dp.pop_front();
dp.push_back(emp[0]);
}
}
int ans = ranges::min(dp[k]);
if (ans == INF) ans = -1;
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
G. Completely Wrong
如果我们将抽出的球看作是求一个 \(1 \dots N\) 的排列 \(P_1, P_2, \dots, P_N\),所有排列等概率出现,共有 \(N!\) 种可能。
我们需要求的是满足 \(C_{P_k} \neq G_k\) 对于所有的 \(1 \le k \le N\) 都成立的排列的数量,最后再除以 \(N!\) 得到概率。
由于限制只发生在同色的球和同色的位置之间,不同颜色的限制是彼此独立的。
设 \(cnt[c]\) 表示颜色 \(c\) 的球的数量,\(req[c]\) 表示颜色 \(c\) 出现在 \(G_k\) 中的次数。
我们可以使用容斥原理。计算出至少有 \(j\) 个球的颜色与目标位置颜色相同的方案数。针对某一种颜色 \(c\),选出 \(j\) 个该颜色的球,并放在 \(j\) 个对应要求为 \(c\) 颜色的位置上,方案数为:$$W(c, j) = \binom{cnt[c]}{j} \times \binom{req[c]}{j} \times j!$$我们可以为每种颜色构造一个多项式 \(P_c(x)\):$$P_c(x) = \sum_{j=0}^{req[c]} W(c, j) x^j$$由于颜色之间相互独立,我们可以将所有颜色的多项式乘起来得到总的多项式 \(P(x)\):$$P(x) = \prod_{c=1}^N P_c(x) = \sum_{j=0}^N A_j x^j$$
这里 \(A_j\) 就是在所有颜色中,总共强制选出 \(j\) 组冲突(同色匹配)的方案数。根据容斥原理,得分为 \(0\) 的排列总数为:$$\text{Total} = \sum_{j=0}^N (-1)^j A_j (N - j)!$$概率即为 \(\frac{\text{Total}}{N!}\)。
\(A_j\) 可以用分治 \(\text{NTT}\) 来求
具体实现的时候可以直接将 \((-1)^j\) 乘进 \(P_c(x)\) 里
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint998244353;
using fps = vector<mint>;
struct modinv {
int n; vector<mint> d;
modinv(): n(2), d({0,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
int n; vector<mint> d;
modfact(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*n), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
int n; vector<mint> d;
modfactinv(): n(2), d({1,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(d.back()*invs(n)), ++n;
return d[i];
}
mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
if (n < k || k < 0) return 0;
return facts(n)*ifacts(k)*ifacts(n-k);
}
vector<int> get(int n) {
vector<int> cnt(n);
rep(i, n) {
int j;
cin >> j;
cnt[j-1]++;
}
return cnt;
}
int main() {
int n;
cin >> n;
auto c = get(n);
auto g = get(n);
queue<fps> q;
rep(i, n) {
fps f;
rep(j, g[i]+1) {
mint now = comb(c[i], j)*comb(g[i], j);
now *= facts(j);
if (j&1) now = -now;
f.push_back(now);
}
q.push(f);
}
while (q.size() > 1) {
auto a = q.front(); q.pop();
auto b = q.front(); q.pop();
q.push(convolution(a, b));
}
mint ans;
rep(i, n+1) ans += q.front()[i] * facts(n-i);
ans *= ifacts(n);
cout << ans.val() << '\n';
return 0;
}
浙公网安备 33010602011771号