做题记录
5.7
| 题号 | Link | 类型 |
|---|---|---|
| CF1083E | The Fair Nut and Rectangles | 斜率优化(板) |
| CF311B | Cats Transport | 斜率优化(板) |
| CF626F | Group Projects | Trick:将极差转为相邻两项差的和(板) |
| CF632 | Thief in a Shop | 价值维背包(也可以FFT),推荐 |
| CF1107E | Vasya and Binary String | 小清新区间dp,有点ad-hoc,推荐 |
| CF35D | Broken robot | 期望dp,但是高斯消元,推荐 |
| CF1628D2 | Game on Sum (Hard Version) | 博弈论,但是从dp转为组合数学很自然,推荐 |
| CF840C | On the Bench | 非常好的性质+插入dp |
| CF771D | Bear and Company | 有一点好玩的转化(从swap转为从最终状态计算贡献) |
| CF248E | Piglet's Birthday | 非常无趣的概率dp |
| CF703E | Mishka and Divisors | Trick :把整除转为 gcd,利用因数个数很小的条件来做,推荐 |
| CF416E | President's Path | Trick:处理与边相关的信息时可以将边的信息转到点上 |
5.11
前面几天先咕咕咕。
Link
杂题
A
令 \(d(i)\) 表示 \(a_i - a_{i+\frac{n}{2}}\),那么发现 \(\Delta d(i) \in \{-2,0,2\}\)。
而 \(d(1) = -d(\frac{n}{2}+1)\),所以可以二分。
C
很明显可以以 \(1\) 为根跑 dfs 那么我们可以计算出从 \(i\) 点出发的最长链和深度。
可以发现 \(x\) 与 \(1\) 距离一半就是分界点,反方一定会选一条最长的路径走,然后直接一个一个往上跳就可以了。
D
好题。
首先抽象成一个矩阵,然后发现就是网格内有一部分的东西已经被填了,并且每行每列都有和的限制。
考虑将行列连边,用边来表示网格图上的点。那么边的数量是 \(O(n^2)\) 的,而只有 \(O(n)\) 个约束,非常恶心。
那么我们对于图上的一个联通分量,我们取它的生成树,其余边全部赋值为 \(0\),现在就只有 \(O(n)\) 个变量了,直接 dfs 完事。
E
没做出来。
首先末状态非常好求,但是考虑到对于步数相同的转化,最大值最大的一定更优秀,然后就把每一个元素转换步数排序过后从小到大分配权值即可。
F
贪心。
首先答案肯定有 \(\sum c_i\),那么考虑个 \(c_i\) 能干吗。
我们将 \(a_i\) 抽象成一个矩形,高度为 \(a_i\),那么我们从 \(i\) 免费(指在 \(c_i\) 的前置下)走到 \(j\) 当且仅当 \(a_j \leq a_i + c_i\)。否则我们要给 \(a_j-a_i-c_i\) 的代价。
那么我们直接对 \(a_i\) 排序,然后记录目前的 \(a_i+c_i\) 的最大值,然后直接扫一遍就可以了。
5.12
A
考虑到值域为 \([0,m]\) 且一行中 \(m\) 个数互不相等,则在一行中只有一个数未出现。
用 \(dp_{i,j}\) 表示在 \(i\) 行中,\(j\) 未出现的方案数。
转移非常简单:
显然这不能直接格路计数,但是当我们把 \(i\) 行的所有数点全部左移 \(i\) 格的时候,就可以做了。
B
容斥。
先弱化条件:抽象为现在有 \(m\) 个长度为 \(k+1\) 的块,要求放在长度为 \(n\) 轴上,问方案数。
很明显的插板,再乘一个 \(2\) 的幂次,就是弱化版的答案。
然而我们发现一棵树占据的块能选择向左倒或者向右倒,而这个东西对于我们本质需要计的数(就是位置)是弱相关的。
也就是说,对于一棵树能左右倒,需要做一个容斥。
直接上二项式反演,钦定有多少个两边都倒,直接做就行。
C
非常好的笛卡尔树+括号序列。
很明显对于这种有关区间最大值位置的东西可以上笛卡尔树,两个序列的同构等价于笛卡尔树的同构。
考虑值域范围的限制:由于值域最大为 \(m\), 所以对于任意节点,它向左走的最远距离不能超过 \(m\)。
考虑对于 \(i\) 点,它的子树能放的数的范围为 \([1,v]\),那么我们可以让左儿子取 \([1,v-1]\),右儿子取 \([1,v]\)。这样一定能构造出一组解使得取遍 \([1,m]\) 的所有数。
那么现在就简单了,三度树转为括号序列,然后括号序列转前缀和,现在就是一个变量,每次可以增加一,也能减少一,但是不能超过 \(-1\) 和 \(m+1\),求方案。
直接容斥即可。
5.13
A
挺板的,只需要把 \(O(n^2)\) 的式子用二项式定理展开就可以。
B
记录一下对于每一个 \(i\),有多少个 \(j\) 有偏序关系,然后 dp+二项式反演就可以。
C~H
水题,咕咕咕。
5.14
还是昨天的比赛。
I
进行了 Hint 的使用,随后做出。
对于恰好问题但是又不好直接计数的,可以考虑二项式反演。
设 \(f(i)\) 表示至少有 \(i\) 个极大的的方案数。设 \(h(i)=(n-i)(m-i)(l-i)\)。
(我们令 \(a(i,j)\) 表示 \(i\) 的 \(j\) 次下降幂)
显然观察得到:\(f(i)=a(n,i) \times a(m,i) \times a(l,i) \times a(nml,h(i))\times g(i)\),其中 \(g(i)\) 是将 \(h(i)\) 个数按照偏序关系排好的方案数。
关键在于 \(g(i)\) 的求法。发现对于小的数,它对于其他格子的约束更严格,所以从小的往大的填。
观察发现,\(g(i)=\prod\limits_{j=1}^i\frac{(h(0)-h(j)-1)!}{(h(0)-h(j-1))!}\)。
然后代回 \(f(i)\) 中,得到:\(f(i)=a(n,i) \times a(m,i) \times a(l,i) \times (nml)!\times \prod\limits_{j=1}^i\frac{1}{(nml-h(j))!}\)。
答案为概率,所以还要除一个 \((nml)!\)。
瓶颈在于求逆元。
放个代码吧:
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 5e6 + 5;
const int Md = 998244353;
ll n, m, l, k, val[N], rev[N], revs[N];
ll qpow(ll a, ll b) {
ll ret = 1;
while (b) {
if (b & 1) ret = (ret * a) % Md;
a = (a * a) % Md;
b >>= 1;
}
return ret;
}
ll F(ll x) {
return (n - x) * (m - x) % Md * (l - x) % Md;
}
ll limit;
void init(void) {
val[0] = 1;
rev[0] = 1;
ll sub = F(0);
for (int i = 1; i <= limit; ++i) {
val[i] = (sub + Md - F(i)) % Md;
rev[i] = (rev[i - 1] * val[i]) % Md;
}
ll invs = qpow(rev[limit], Md - 2);
for (int i = limit; i; --i) {
rev[i] = invs * rev[i - 1] % Md;
invs = (invs * val[i]) % Md;
}
}
void pre(void) {
revs[0] = 1;
for (int i = 1; i < N; ++i)
revs[i] = (revs[i - 1] * i) % Md;
ll invs = qpow(revs[N - 1], Md - 2);
for (int i = N - 1; i > 0; --i) {
revs[i] = invs * revs[i - 1] % Md;
invs = (invs * i) % Md;
}
}
void solve(void) {
cin >> n >> m >> l >> k;
limit = min({ n, m, l });
init();
ll C = 0, S = 1, ret = 0;
for (int i = 1; i <= limit; ++i) {
S = (S * F(i - 1) % Md * rev[i]) % Md;
if (i == k) C = 1;
if (i > k) C = (Md - C * i % Md * revs[i - k] % Md) % Md;
ret = (ret + C * S % Md) % Md;
}
cout << ret << '\n';
}
int main() {
FASTIO;
pre();
int t;
cin >> t;
while (t--) solve();
return 0;
}
K
好像很牛的样子。
没做出来。
考虑刻画这个括号序列的价值,可以发现,我们肯定可以在一个分界点把左边的右括号都删去,然后把右边的所有左括号都删掉,然后价值就可以刻画出来了。
我们直接在分界点上计数,设在当前分界点处左侧有 \(l\) 个左括号,有 \(x\) 个问号,右侧有 \(r\) 个左括号,\(y\) 个问号。
显然我们有贡献:\(\sum\limits_{i=0}^x(l+x)\binom{x}{i}\binom{y}{l+i-r}\)。
然后化简一下式子,再用一下 Vandermonde 卷积,得到 :
这样就可以 \(O(n)\) 计算了。
也是给一个代码:
#include <bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 4e6 + 5;
const int Md = 998244353;
ll fac[N], rev[N];
int pre[2][N], suf[2][N];
ll qpow(ll a, ll b) {
ll ret = 1;
while (b) {
if (b & 1) ret = (ret * a) % Md;
a = (a * a) % Md;
b >>= 1;
}
return ret;
}
void init(void) {
fac[0] = rev[0] = 1;
for (int i = 1; i < N; ++i) {
fac[i] = (fac[i - 1] * i) % Md;
rev[i] = (rev[i - 1] * qpow(i, Md - 2)) % Md;
}
}
ll C(int n, int m) {
if (n < m || n < 0) return 0;
return fac[n] * rev[m] % Md * rev[n - m] % Md;
}
int main() {
FASTIO;
init();
string s;
cin >> s;
s = " " + s;
for (int i = 1; i < s.size(); ++i) {
pre[0][i] = pre[0][i - 1] + (s[i] == '?');
pre[1][i] = pre[1][i - 1] + (s[i] == '(');
}
for (int i = s.size() - 1; i; --i) {
suf[0][i] = suf[0][i + 1] + (s[i] == '?');
suf[1][i] = suf[1][i + 1] + (s[i] == ')');
}
ll ret = 0;
for (int i = 1; i < s.size(); ++i) {
ll add = pre[1][i - 1] * C(pre[0][i - 1] + suf[0][i], suf[1][i] + suf[0][i] - pre[1][i - 1]) % Md;
add = (add + pre[0][i - 1] * C(pre[0][i - 1] + suf[0][i] - 1, suf[1][i] + suf[0][i] - pre[1][i - 1] - 1) % Md) % Md;
ret = (ret + add) % Md;
}
cout << ret << '\n';
return 0;
}

浙公网安备 33010602011771号