dmy 补题记录
DMY D6
T123 放在题 1 里。
T4
非常阴的题。
首先整除函数把除以 \(1\) 拿掉,剩下的如果乘积 \(> x\) 则答案为 \((n + m)!\)。特判完整除函数只有 \(\mathcal{O}(\log x)\) 个了。
然后考虑转化为概率问题,\(f_{s, i}\) 表示整除函数使用的集合为 \(s\) 当前数为 \(i\) 的概率。答案为 \(f_{2^n-1, 0} \times (n + m)!\) 显然对于 \(> i\) 的取余函数没有作用,所以我们只考虑 \(\le i\) 的取余函数,然后暴力转移,就过了。。。。。。。。
看似是 \(\mathcal{O}(x^3)\) 的 dp, 实则我们考虑 \(s\) 的 popcount 为 \(cnt\),\(i\) 是 \(\mathcal{O}(x \times 2^{-cnt})\) 的,枚举整除函数直接忽略,取余函数也是 \(\mathcal{O}(x \times 2^{-cnt})\) 的,总计 \(x^2 \sum 2^{-i}\) = \(\mathcal{O}(x^2)\), 可以通过。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, m, x, a[N], b[N], cnt[N], sum[N];
ll fac[N], f[1 << 14][10010];
ll qpow(ll a, int b) {
ll res = 1;
for (; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod;
return res;
}
template<typename T>
void add(T &x, T y) { x += y; if (x >= mod) x -= mod; }
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> x >> n >> m;
fac[0] = 1;
for (int i = 1; i <= n + m; i++) fac[i] = fac[i - 1] * i % mod;
ll res = fac[n + m];
for (int i = 1, mx = 1; i <= n; i++) {
cin >> a[i];
if (a[i] == 1) {
swap(a[i], a[n]);
n--; i--;
continue;
}
mx *= a[i];
if (mx > x) {
cout << res << '\n';
return 0;
}
}
for (int i = 1; i <= m; i++) cin >> b[i], ++cnt[b[i]];
for (int i = 1; i <= x; i++) sum[i] = sum[i - 1] + cnt[i];
f[0][x] = 1;
for (int s = 0; s < (1 << n); s++) {
for (int i = x; i >= 0; i--) if (f[s][i]) {
ll cur = qpow(n - __builtin_popcount(s) + sum[i], mod - 2);
ll d = f[s][i] * cur % mod;
for (int j = 1; j <= n; j++) if (!(s >> (j - 1) & 1)) {
add(f[s | (1 << j - 1)][i / a[j]], d);
}
for (int j = 1; j <= i; j++) if (cnt[j]) {
add(f[s][i % j], d * cnt[j] % mod);
}
}
}
cout << (res * f[(1 << n) - 1][0] % mod) << '\n';
return 0;
}
DMY D7
T1
比较有意思的题。考虑正着反着都来一遍在中间汇合即可。还有一种做法是大力分讨。
T2
糖丸了,每次找到位置之后 kth 即可,找规律 + ds 题。
T3
我不会。
- 多源 bfs,有两个数有恰好一位不同就连边,\(ans_i = dis_{all \oplus a_i}\)
- \(f_{i, j}\) 表示 \(a_i\) 前 \(j\) 位相同后面随便最多几位不同。类似 Floyd 一样一步步放宽限制从而把环的问题解决
上述两种是 dp 转移成环的两种典型解决方案。
T4
原题忘了,抽出所有区间,排序后 dp 是 \(n^2\) 的,然后每个区间最多比前一个区间长 \(1\), 所以长度至多 \(\mathcal{O}(\sqrt n)\), 字典树上 dfs 加线段树即可。根号老哥跑的飞慢。细节是长度是二倍根号的,开成一倍会寄,但是 dmy 给过了,强大的数据。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2.5e4 + 10, B = 250;
typedef long long ll;
typedef pair<int, int> pii;
int n, dp[N], tot, rk[N], mx;
char s[N];
vector<pii>vec[N * B];
struct SegTree {
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
int tree[N << 2];
void pushup(int pos) { tree[pos] = max(tree[ls(pos)], tree[rs(pos)]); }
void modify(int x, int k, int l = 1, int r = n, int pos = 1) {
if (l == r) {
tree[pos] = max(tree[pos], k);
return ;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(x, k, l, mid, ls(pos));
else modify(x, k, mid + 1, r, rs(pos));
pushup(pos);
}
int rangeMax(int x, int y, int l = 1, int r = n, int pos = 1) {
if (x > y) return 0;
if (x <= l && r <= y) return tree[pos];
int mid = (l + r) >> 1, res = 0;
if (x <= mid) res = max(res, rangeMax(x, y, l, mid, ls(pos)));
if (mid < y) res = max(res, rangeMax(x, y, mid + 1, r, rs(pos)));
return res;
}
} st;
struct Trie {
int nxt[N * B][2], cnt = 0;
void insert(int l, int r) {
int pos = 0;
for (int i = l; i <= r; i++) {
if (!nxt[pos][s[i] - '0']) nxt[pos][s[i] - '0'] = ++cnt;
pos = nxt[pos][s[i] - '0'];
vec[pos].push_back({l, i});
}
}
void dfs(int u) {
for (auto [l, r] : vec[u]) dp[r] = max(dp[r], st.rangeMax(1, l - 1) + 1);
for (auto [l, r] : vec[u]) st.modify(r, dp[r]);
if (nxt[u][0]) dfs(nxt[u][0]);
if (nxt[u][1]) dfs(nxt[u][1]);
}
} trie;
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++)
trie.insert(i, min(n, i + B - 1));
trie.dfs(0);
printf("%d\n", st.rangeMax(1, n));
return 0;
}
DMY DAY8
T1

\(n \le 10^5\)。
考虑贪心,对于 \(u\) 的每个子节点 \(v\) 按 \(v\) 子树内叶子节点的最大深度升序排序,因为子树里面走最后肯定是到最深的叶子。然后记一个 \(pre_u\) 表示 \(u\) 走完之后派出的兵的深度,每次只需要分别派一个兵到所有子节点就行,可以是从上一个子节点的 \(pre\) 过来也可以是从根节点过来,一开始 \(pre_u = dep[u]\) 因为 dfs 到 \(u\) 的时候 \(u\) 已经有一个兵了,每次走完 dfs 子节点,更新 \(pre_u \leftarrow pre_v\) 即可。其实证明感觉不太容易,但是猜到就赢了。
时间复杂度 \(\mathcal{O}(n \log n)\)。
点击查看代码
bool cmp(const int &u, const int &v) {
return hig[u] < hig[v];
}
inline void dfs1(int u, int fa) {
hig[u] = dep[u];
for (auto v : e[u]) if (v != fa) {
dep[v] = dep[u] + 1;
dfs1(v, u);
hig[u] = max(hig[u], hig[v]);
}
sort(e[u].begin(), e[u].end(), cmp);
}
inline void dfs2(int u, int fa) {
pre[u] = dep[u]; // 钦定一开始 u 上已经有一个从子树外来的点了
for (auto v : e[u]) if (v != fa) {
ans += min(dep[u] + 1, pre[u] - dep[u] + 1); // 送一个点到 v
dfs2(v, u);
pre[u] = pre[v];
}
}
T2
首先一个显然的做法是二分答案之后每次从堆中找出最大的点再看看相邻的点是否需要更新,因为最大的点是不会再变的。时间复杂度 \(\mathcal{O}(rc \log {rc} \log V)\) 带超大 \(6\) 倍常数。然后过了何意味。。。虽然我也是这个做法()。
点击查看代码
bool check(ll d) {
priority_queue<Node>q;
ll sum = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
q.push({a[i][j], i, j});
}
}
while (!q.empty()) {
auto [v, x, y] = q.top(); q.pop();
for (int i = 0; i < 6; i++) {
int xx = x + D[i][0], yy = y + D[i][1];
if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
if (v - a[xx][yy] > d) sum += v - d - a[xx][yy], a[xx][yy] += v - d - a[xx][yy], q.push({a[xx][yy], xx, yy});
}
}
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) a[i][j] = b[i][j];
return sum <= k;
}
考虑优化掉 \(\log rc\) 注意到每次放进去的点都是最大的点 \(-d\), 这个东西是不增的,直接放队列里就行,这样就只需要保证一开始的元素有序了,时间复杂度 \(\mathcal{O}(rc \log V)\) 轻松通过。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 10;
constexpr int D[6][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, 1}, {1, -1}};
typedef long long ll;
typedef pair<int, int> pii;
struct Node {
ll v;
int x, y;
bool operator < (const Node &A) const {
return v < A.v;
}
} c[N];
int n, m;
ll k;
vector<vector<ll>>a, b;
bool check(ll d) {
queue<Node>q;
ll sum = 0;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) a[i][j] = b[i][j];
for (int i = n * m; i >= 1; i--) q.push(c[i]);
while (!q.empty()) {
auto [v, x, y] = q.front(); q.pop();
for (int i = 0; i < 6; i++) {
int xx = x + D[i][0], yy = y + D[i][1];
if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
if (v - a[xx][yy] > d) sum += v - d - a[xx][yy], a[xx][yy] += v - d - a[xx][yy], q.push({a[xx][yy], xx, yy});
if (sum > k) return 0;
}
}
return sum <= k;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
scanf("%d%d%lld", &n, &m, &k);
a.resize(n + 1), b.resize(n + 1);
for (int i = 1; i <= n; i++) {
a[i].resize(m + 1);
b[i].resize(m + 1);
for (int j = 1; j <= m; j++) {
scanf("%lld", &a[i][j]);
b[i][j] = a[i][j];
c[(i - 1) * m + j] = {a[i][j], i, j};
}
}
sort(c + 1, c + (n * m) + 1);
ll L = -1, R = 1e12 + 1;
while (L + 1 < R) {
ll mid = (L + R) >> 1;
if (check(mid)) R = mid;
else L = mid;
}
printf("%lld\n", R);
return 0;
}
T3
较为复杂的数位 dp 题,有比较好的练习意义。具体直接看代码吧。
点击查看代码
int T, n, L, R, a, b, f[N][N][2][2], k; // f[i][j][0/1][0/1]: 前 i 位有 j 个 1, 是否顶 L(下界)/R(上界) 的数字个数, g为数字之和
ll g[N][N][2][2], sum;
inline pil dfs(int x, int cnt, int l1, int l2) {
if (x == -1) return {!cnt, 0};
if (~f[x][cnt][l1][l2]) return {f[x][cnt][l1][l2], g[x][cnt][l1][l2]};
f[x][cnt][l1][l2] = g[x][cnt][l1][l2] = 0;
int down = l1 ? (L >> x & 1) : 0, up = l2 ? (R >> x & 1) : 1;
for (int i = up; i >= down; i--) {
if (cnt >= i) {
auto [u, v] = dfs(x - 1, cnt - i, l1 & (i == down), l2 & (i == up));
f[x][cnt][l1][l2] += u, g[x][cnt][l1][l2] += v + 1ll * (i << x) * u;
}
}
return {f[x][cnt][l1][l2], g[x][cnt][l1][l2]};
}
int calc(int x, int cnt, int l1, int l2) {
if (!k || f[x][cnt][l1][l2] == -1) return 0;
if (f[x][cnt][l1][l2] <= k) {
k -= f[x][cnt][l1][l2];
sum += g[x][cnt][l1][l2];
return f[x][cnt][l1][l2];
}
int down = l1 ? (L >> x & 1) : 0, up = l2 ? (R >> x & 1) : 1, s = 0;
for (int i = up; i >= down; i--) {
if (cnt >= i && k) {
int u = calc(x - 1, cnt - i, l1 & (i == down), l2 & (i == up));
s += u, sum += 1ll * (i << x) * u;
}
}
return s;
}
ll solve(int L, int R, int p) {
if (!p) return 0ll;
k = p; sum = 0;
for (int i = 30; i >= 0 && k; i--) {
dfs(30, i, 1, 1);
if (k >= f[30][i][1][1]) {
k -= f[30][i][1][1];
sum += g[30][i][1][1];
} else {
calc(30, i, 1, 1);
break;
}
}
return sum;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> T;
while (T--) {
memset(f, 255, sizeof f);
cin >> L >> R >> a >> b;
cout << solve(L, R, b) - solve(L, R, a - 1) << '\n';
}
return 0;
}
T4
这也太难了。原题:https://codeforces.com/gym/102769/problem/H
考虑平方这个东西的组合意义:假设你有一个序列,里面有 \(cnt\) 个 \(t\), 从这些 \(t\) 里可重复、有序地选两个出来的方案数就是 \(cnt^2\), 然而这玩意太难维护了,可以拆一下,\(x^2=2\binom{x}{2}+\binom{x}{1}\), 然后我们需要考虑后面这两项。
还是好难啊,我连 \(\binom{x}{1}\) 都没法求咋办。
观察一下,你发现如果在对序列进行 dp 的过程中一定要记 \(i, j\) 表示当前到第 \(i\) 位前缀 \(\max\) 为 \(j\), 这样的话状态已经 \(\mathcal{O}(n^2)\) 了,很难维护每一个数的出现次数。
这启示我们可以枚举一个数 \(t\) 以及它第一次出现的位置 \(i\)(注意由题目的限制 \(i \ge t\)), 然后根据 \(i\) 左边、右边的序列个数统计 \(ans_t\)。
所以就有了 \(f_{i, j}, g_{i, j}\) 分别表示长度为 \(i\) 的序列,开头/结尾为 \(j\) 的方案数。转移与第二类斯特林数类似,这里写 \(g\) 的:
然后考虑合并:枚举 \(t, i\),讨论我们选择的两个位置 \(x, y(x \le y)\) 满足 \(p_x = p_y = t\),分下列四种情况:
- \(x = y = i\), 有 \(f_{i - 1, t} \times g_{n - i + 1, t}\) 种。
- \(x = i, x \neq y\), 有 \(2f_{i - 1, t} \times (n - i) g_{n - i, t}\) 种,乘 \(2\) 是因为有序数对。
- \(x = y, x \neq i\), 有 \(f_{i - 1, t} \times (n - i) g_{n - i, t}\) 种。
- \(x \neq y, x \neq i, y \neq i\), 有 \(2f_{i - 1, t} \times \binom{n - i}{2} g_{n - i - 1, t}\) 种。
这里解释一下为什么可以随便的插入一个或两个 \(t\), 因为插入之后前缀最大值一定不会变大(\(x\) 之前已经出现过 \(t\) 了)。
时间复杂度 \(\mathcal{O}(n^2)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 3e3 + 10;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, mod;
ull f[N][N], g[N][N];
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> mod;
f[0][0] = 1;
for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) f[i][j] = (f[i - 1][j] * j + f[i - 1][j - 1]) % mod;
for (int i = 1; i <= n; i++) g[1][i] = 1;
for (int i = 2; i <= n; i++) for (int j = 1; j <= n - i + 1; j++) g[i][j] = (g[i - 1][j] * j + g[i - 1][j + 1]) % mod;
for (int t = 1; t <= n; t++) {
ull ans = 0;
for (int i = t; i <= n; i++) {
ans += f[i - 1][t - 1] * g[n - i + 1][t] % mod; ans %= mod;
if (i < n) ans += f[i - 1][t - 1] * (n - i) % mod * g[n - i][t] % mod * 2 % mod, ans %= mod;
if (i < n) ans += f[i - 1][t - 1] * (n - i) % mod * g[n - i][t] % mod, ans %= mod;
if (i < n - 1) ans += f[i - 1][t - 1] * (n - i) * (n - i - 1) % mod * g[n - i - 1][t] % mod, ans %= mod;
}
cout << ans << " \n"[t == n];
}
return 0;
}
DMY DAY9
T1
考虑先差分,区间加变差分数组上两个单点加,然后考虑 dp, \(f_{i, j, k}\) 表示前 \(i\) 个 \(j\) 个 \(+1\), \((j - k)\) 个 \(-1\), 转移比较显然,就变成 \(\mathcal{O}(nm^4)\) 的了,然后将 \(+1\) 和 \(-1\) 分开转移就变成 \(\mathcal{O}(nm^3)\) 的了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 10, M = 3e2 + 10, mod = 998244353;
typedef long long ll;
typedef pair<int, int> pii;
int n, m, a[N], b[N], tmp[M][M], dp[N][M][M]; //dp[i][j][k]: 考虑前 i 个, 差分数组上有 j 个 1, (j - k) 个 -1
int C[M][M];
void add(int &x, int y) {
x += y;
if (x >= mod) x -= mod;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
C[0][0] = 1;
for (int i = 1; i <= m; i++) C[i][0] = C[i][i] = 1;
for (int i = 1; i <= m; i++) {
for (int j = 1; j < i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= j; k++) {
tmp[j][k] = 0;
for (int p = 0; p <= k; p++) {
add(tmp[j][k], 1ll * C[j][p] * dp[i - 1][j - p][k - p] % mod);
// if (i == 1 && j == 1 && k == 1) {
// cout << "tmp: " << p << ' ' << dp[i - 1][j - p][k - p] << '\n';
// }
}
}
for (int k = 0; k <= j; k++) {
dp[i][j][k] = 0;
for (int q = max(a[i] - k, 0); q <= min(b[i], j) - k; q++) {
// if (i == 1 && j == 1 && k == 1) {
// cout << "q: " << b[i] << ' ' << k + q << ' ' << tmp[j][k + q] << '\n';
// }
add(dp[i][j][k], 1ll * C[k + q][q] * tmp[j][k + q] % mod);
}
}
}
}
cout << dp[n][m][0] << '\n';
return 0;
}
T2
考虑题目条件等价于差分之后区间 \(\gcd > 1\), 这时可行的 \(m\) 为 \(m | \gcd\), 使用 \(ST\) 表维护并二分答案可以做到 \(n \log^2 n\)。然后能过 2e6/xia,优化就是 baka's trick,详见我 tricks 那篇里搬的。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10, M = 2e3;
typedef long long ll;
typedef pair<int, int> pii;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, mid = 1;
ll a[N], b[N], g[M + 10][M + 10], s[N], t[N];
ll gcd(ll a, ll b) {
if (a <= M && b <= M && g[a][b]) return g[a][b];
ll res = b == 0 ? a : gcd(b, a % b);
if (a <= M && b <= M) g[a][b] = res;
return res;
}
ll ask(int l, int r) {
if (l > mid) {
mid = r;
for (int i = mid; i >= l; i--) s[i] = gcd(s[i + 1], b[i]);
t[mid] = 0;
}
if (r > mid) t[r] = gcd(t[r - 1], b[r]);
return gcd(s[l], t[r]);
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (i != 1) b[i - 1] = abs(a[i - 1] - a[i]);
}
n--;
int ans = 1;
for (int l = 1, r = 1; r < n; l++) {
r = max(r, l + ans - 2);
while (r < n && ask(l, r + 1) > 1) r++;
if (ask(l, r) > 1) ans = max(ans, r - l + 2);
}
cout << ans << '\n';
return 0;
}
T3
原:CF1548C
题目所求即是 \(\sum\limits_{i=1}^n \dbinom{it}{k}\), 其中 \(k\) 为题目中的 \(x\)。
注意到这玩意是 \([x^k]\sum\limits_{i=1}^{nt}(1+x)^{i}\), 根据等比数列求和,就是 \(\dfrac{(1+x)^{nt+t}-(1+x)^t}{(1+x)^t-1}\), 然后模拟一下多项式除法(就是列竖式求那个)即可,时间复杂度 \(\mathcal{O}(nt^2)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 1e7 + 20, mod = 1e9 + 7;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, t, m, Q;
ull fac[N], ifac[N], a[N], b[N], c[N];
ull qpow(ull a, int b) {
ull res = 1;
for (; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod;
return res;
}
ull C(int n, int m) {
if (n < m || n < 0 || m < 0) return 0llu;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void work(ull a[], int n, int sgn) {
for (int i = 0; i <= n; i++) {
a[i] = (a[i] + C(n, i) * sgn + mod) % mod;
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> t >> Q;
int m = n * t + t;
fac[0] = ifac[0] = 1;
for (int i = 1; i <= m; i++) fac[i] = fac[i - 1] * i % mod;
ifac[m] = qpow(fac[m], mod - 2);
for (int i = m - 1; i; i--) ifac[i] = ifac[i + 1] * (i + 1) % mod;
work(a, m, 1); work(a, t, -1); work(b, t, 1); b[0] = (b[0] + mod - 1) % mod;
for (int i = m; i >= t; i--) {
int div = a[i] / b[t];
c[i - t] = div;
if (div) for (int j = 0; j <= t; j++) a[i - j] = (a[i - j] - div * b[t - j] % mod + mod) % mod;
}
while (Q--) {
int k;
cin >> k;
cout << c[k] << '\n';
}
return 0;
}
T4
原:CF1548E
考虑一个很牛的 trick: 对于一个连通块,设定一个唯一的代表元,那么连通块的个数就可以用代表元的个数来表示。
最小的点是最有可能黑的,假设一个连通块中 \((a_i + b_j, i, j)\) 字典序最小的那个点为代表元。
首先考虑第一项更小的点,设 \(la_i\) 表示 \(i\) 之前第一个 \(a_j \le a_i\) 的 \(j\), \(ra_i\) 表示 \(i\) 之后第一个 \(a_j > a_i\) 的 \(j\), 对 \(b\) 进行同样的定义。则 \((la_i, j), (ra_i, j), (i, lb_j), (i, rb_j)\) 这四个点是优于 \((i, j)\) 的,且最容易到达的。如果要让 \((i, j)\) 为代表,则 \((i, j)\) 和这四个点直接一定不可达。
比如 \((la_i, j)\), 不可达说明中间有点断开,说明 \(\max\limits_{k=la_i+1}^{i-1} a_k + b_j > x\), 同时对于 \((ra_i, j)\) 有 \(\max\limits_{k=i+1}^{ra_i-1} a_k + b_j > x\), 记这俩玩意是 \(va_i + b_j\), 同理有 \(vb_j\), 那么就有了下面三个约束:
读到这里读者肯定会有疑问,为什么一定走直线呢?假设走了曲线,比如先从 \((i, j)\) 走到了 \((i, p)\) 再到 \((i - 1, p)\) 再到 \((la_i, j)\), 那么由于 \(b_p\) 肯定 \(\ge b_j\) 所以 \(a_{i - 1}+b_p \ge a_{i - 1} + b_j\), 也就是说如果你走到了 \((i - 1, p)\), 那 \((i - 1, j)\) 也一定是黑的,所以一旦有一个可以上移的地方,就一定可以通过走直线上去,这只是向上的情况,其它三个方向也是同理的。
移项,和 \(i\) 有关的放左边,二维数点即可。
时间复杂度 \(\mathcal{O}(n \log n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 2e5 + 10, inf = 1 << 30;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, m, x, a[N], b[N], st[20][N], stk[N], top, l[N], r[N], tot;
ll ans;
struct Node {
int x, y, typ;
bool operator < (const Node &A) const {
return {x == A.x ? typ > A.typ : x > A.x};
}
} c[N << 1];
struct Fenwick {
#define lowbit(x) (x & -x)
int n, tr[N];
void add(int x, int v) { while (x <= n) { tr[x] += v; x += lowbit(x); } }
int ask(int x) { int res = 0; while (x > 0) { res += tr[x]; x -= lowbit(x); } return res; }
} BIT;
int query(int l, int r, int up) {
if (!l || r == up + 1) return inf;
int x = __lg(r - l + 1);
return max(st[x][l], st[x][r - (1 << x) + 1]);
}
void init() {
for (int i = 1; i <= n; i++) st[0][i] = a[i];
for (int d = 1; d < 20; d++) for (int i = 1; i <= n - (1 << d) + 1; i++) st[d][i] = max(st[d - 1][i], st[d - 1][i + (1 << (d - 1))]);
top = 0; stk[top] = 0;
for (int i = 1; i <= n; i++) {
while (top && a[stk[top]] > a[i]) top--;
l[i] = stk[top]; stk[++top] = i;
}
top = 0; stk[top] = n + 1; // r[i] 为 0 时变为 n + 1
for (int i = n; i; i--) {
while (top && a[stk[top]] >= a[i]) top--;
r[i] = stk[top]; stk[++top] = i;
}
for (int i = 1; i <= n; i++) c[++tot] = {min(query(l[i], i, n), query(i, r[i], n)), a[i], 0};
for (int i = 1; i <= m; i++) st[0][i] = b[i];
for (int d = 1; d < 20; d++) for (int i = 1; i <= m - (1 << d) + 1; i++) st[d][i] = max(st[d - 1][i], st[d - 1][i + (1 << (d - 1))]);
top = 0; stk[top] = 0;
for (int i = 1; i <= m; i++) {
while (top && b[stk[top]] > b[i]) top--;
l[i] = stk[top]; stk[++top] = i;
}
top = 0; stk[top] = m + 1;
for (int i = m; i; i--) {
while (top && b[stk[top]] >= b[i]) top--;
r[i] = stk[top]; stk[++top] = i;
}
for (int i = 1; i <= m; i++) c[++tot] = {x - b[i], x - min(query(l[i], i, m), query(i, r[i], m)), 1};
}
void solve() {
sort(c + 1, c + tot + 1);
for (int i = 1; i <= tot; i++) {
auto [x, y, typ] = c[i];
if (typ) {
ans += BIT.ask(x) - BIT.ask(y);
} else BIT.add(y, 1);
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> x; BIT.n = x;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) cin >> b[i];
init(); solve();
cout << ans << '\n';
return 0;
}
DMY DAY10
T1
宝宝题,枚举第一行换到哪里了,从而得出列的交换序列,在根据列的情况得出行即可,时间复杂度 \(\mathcal{O}(n^3)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e2 + 10, inf = 1 << 30;
typedef long long ll;
typedef pair<int, int> pii;
int n, a[N][N], b[N][N], r[N], c[N], ans = inf;
// r[i]: 第 i 行实际上是哪行
// c[i]: 第 i 列实际上是哪列
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &b[i][j]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) r[j] = j;
for (int j = 1; j <= n; j++) c[j] = j;
swap(r[1], r[i]);
int res = i > 1;
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
if (a[r[1]][c[k]] == b[1][j]) {
res += j != k;
swap(c[j], c[k]);
}
}
}
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
if (a[r[k]][c[1]] == b[j][1]) {
res += j != k;
swap(r[j], r[k]);
}
}
}
bool equ = true;
for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) equ &= a[r[j]][c[k]] == b[j][k];
if (equ) ans = min(ans, res);
}
printf("%d\n", ans == inf ? -1 : ans);
return 0;
}
T2
Too hard。原:hihoCoder挑战赛15 C
考虑 \(f_u, g_u\) 分别表示 \(u\) 子树内覆盖完,对/不对 \(u\) 进行操作 \(2\) 的答案。\(rst_u\) 为是否存在一条向上延伸的路径。
显然 \(f_u = \sum_{v \in son_u} g_v\), 考虑 \(g\) 的转移,对于 \(v \in son_u\), 如果 \(f_v \le g_v - rst_v\) 则直接加 \(f_v\), 因为不需要使用路径,而后者需要使用。假设加后者的有 \(cnt\) 个,那 \(g_u\) 就要加 \(\lceil \dfrac{cnt}{2} \rceil\), 并且如果 \(cnt\) 为奇数则 \(rst_u=1\), 如果不是奇数连了没意义。
时间复杂度 \(\mathcal{O}(n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 2e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
int n, f[N], g[N], rst[N];
vector<int>e[N];
inline void dfs(int u, int fa) {
f[u] = 1; int cnt = 0;
for (auto v : e[u]) if (v != fa) {
dfs(v, u);
f[u] += g[v];
if (f[v] <= g[v] - rst[v]) g[u] += f[v];
else cnt++, g[u] += g[v] - rst[v];
}
g[u] += (cnt + 1) / 2;
if (cnt & 1) rst[u] = 1;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].push_back(v); e[v].push_back(u);
}
dfs(1, 0);
cout << min(f[1], g[1]) << '\n';
return 0;
}
T3
Too hard。原: https://acm.hdu.edu.cn/showproblem.php?pid=6122
考虑原条件的一个必要条件:每个 \(2 \times 2\) 的矩形有 \(2\) 蓝 \(2\) 红,显然这个条件也是原条件的充分条件。这个条件非常强,启发我们可以用前一行推出下一行。
记红色为 \(R\) 蓝色为 \(B\) 那么考虑第一行有两种情况:
- \(RBRBRBRBR\cdots\) 即两两相异,那么下一行要么和这一行完全相同,要么完全相异。
- \(RBBRBRRBR\cdots\) 即存在一些位置相同,如果存在两个位置相同,那么下一行这两个位置一定和这一行相异,从而推出所有位置都与这一行相异,即有一个相同的位置就能推出下一行。
对于第一种情况,有冲突方案数为 \(0\), 如果一行没有冲突那就只有一种方案,空行则有两种,所以方案数为 \(2^cnt\), 其中 \(cnt\) 为空行个数。
对于第二种情况,先将有涂色的列全部弄好,然后只考虑第一行即可,答案为空白格子的个数,最后要减去第一种里面统计过的(即 \(RBRBRB\cdots\) 或 \(BRBRBR\cdots\))。
时间复杂度 \(\mathcal{O}(nm)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
typedef long long ll;
typedef pair<int, int> pii;
int n, m, vis[N];
char s[N][N];
int qpow(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % mod) if (b & 1) res = 1ll * res * a % mod;
return res;
}
int calc1() {
int cnt = 0;
for (int i = 1; i <= n; i++) {
bool odr = 0, odb = 0, evr = 0, evb = 0, flag = 1;
for (int j = 1; j <= m; j++) {
if (s[i][j] == 'R') {
if (j & 1) {
if (!odb) odr = 1;
else return 0;
} else {
if (!evb) evr = 1;
else return 0;
}
flag = 0;
}
if (s[i][j] == 'B') {
if (j & 1) {
if (!odr) odb = 1;
else return 0;
} else {
if (!evr) evb = 1;
else return 0;
}
flag = 0;
}
}
if (flag) cnt++;
}
return qpow(2, cnt);
}
int calc2() {
memset(vis, 255, sizeof vis);
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= n; i++) {
bool odr = 0, odb = 0, evr = 0, evb = 0;
if (s[i][j] == 'R') {
if (i & 1) {
if (!odb) odr = 1;
else return 0;
} else {
if (!evb) evr = 1;
else return 0;
}
vis[j] = i & 1;
} else if (s[i][j] == 'B') {
if (i & 1) {
if (!odr) odb = 1;
else return 0;
} else {
if (!evr) evb = 1;
else return 0;
}
vis[j] = !(i & 1);
}
}
}
int cnt = 0;
bool odr = 0, odb = 0, evr = 0, evb = 0;
for (int i = 1; i <= m; i++) {
if (vis[i] == 1) {
if (i & 1) odr = 1;
else evr = 1;
} else if (vis[i] == 0) {
if (i & 1) odb = 1;
else evb = 1;
} else cnt++;
}
int d = 0;
if (!odb && !evr) d++;
if (!evb && !odr) d++;
return (qpow(2, cnt) - d + mod) % mod;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> (s[i] + 1);
}
cout << (calc1() + calc2()) % mod << '\n';
return 0;
}
T4
Too hard。
首先对于单个点的期望是好求的,\(f(i) = \dfrac{f(i+lowbit(i))+f(i-lowbit(i))}{2}+1\), 记忆化即可。是单 \(\log\) 的。
考虑奇偶分开考虑。
对于奇数:
而由于 \(f(x) = f(2x)\)(左移一位没有影响),所以原式等于一个常数加 \(f(2k+2)\) 加数据范围一半的答案。
对于偶数,直接全部除以 \(2\), 也变成了数据范围一半的答案。
总时间复杂度 \(\mathcal{O}(\log^2 n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, mod = 998244353;
typedef long long ll;
typedef pair<int, int> pii;
#define lowbit(x) (x & (-x))
int qpow(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % mod) if (b & 1) res = 1ll * res * a % mod;
return res;
}
int inv2 = qpow(2, mod - 2), cnt = 0;
ll L, R;
map<ll, ll>mp, vis;
ll f(ll x) {
if (!x) return 0;
if (x == lowbit(x)) return 2;
if (mp[x]) return mp[x];
return mp[x] = (1 + ((f(x + lowbit(x)) + f(x - lowbit(x))) % mod) * inv2 % mod) % mod;
}
ll calc(int n) {
// f(1) + f(3) + ... + f(2k + 1) (k \in Z) = 1 + (f(1 + lowbit(1)) + f(1 - lowbit(1))) / 2 + 1 + (f(3 + lowbit(3)) + f(3 + lowbit(3))) / 2
// + ... + 1 + (f(2k + 2) + f(2k)) / 2 = k + 1 + (f(0) + 2f(2) + 2f(4) + ... + 2f(2k) + f(2k + 2)) / 2 = k + 1 + 0 + \sum_{i = 1}^{k} f(k) + f(2k + 2) / 2
// f(2) + f(4) + ... + f(2k) (k \in Z) = \sum_{i = 1}^{k} f(k)
// 这样转化每次规模 / 2, 只要多算一个 f(2k + 2), 时间复杂度 O(log^2 n)
if (vis[n]) return vis[n];
if (!n) return 0;
if (n == 1) return f(1);
int k = n % 2 ? n / 2 : (n / 2) - 1;
if (k < 0) k = 0;
// cout << "k: " << k << '\n';
return vis[n] = ((calc(k) + calc(n / 2)) % mod + k + 1 + (f(2 * k + 2) * inv2) % mod) % mod;
}
int main() {
// ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> L >> R;
cout << (calc(R) - calc(L - 1) + mod) % mod << '\n';
return 0;
}
DMY DAY5'
T1
糖丸了,建个分层图第二层连边权为 \(0\) 的边,跑 01bfs 即可,时间复杂度 \(\mathcal{O}(n + m + V \log V)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, M = 20;
typedef long long ll;
typedef pair<int, int> pii;
int n, m, a[N], mx, dis[(N << 1) + (1 << M)];
vector<pii>e[(N << 1) + (1 << M)];
void bfs() {
memset(dis, 0x3f, sizeof dis);
deque<int>q;
dis[1] = 0;
q.push_back(1);
while (!q.empty()) {
int u = q.front(); q.pop_front();
for (auto [v, l] : e[u]) {
if (dis[v] > dis[u] + l) {
dis[v] = dis[u] + l;
if (l) q.push_back(v);
else q.push_front(v);
}
}
}
for (int i = 1; i <= n; i++) printf("%d\n", dis[i] == 0x3f3f3f3f ? -1 : dis[i]);
}
int main() {
// freopen("ex_walk2.in", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
mx = max(mx, a[i]);
e[i].push_back({n + a[i] + 1, 1});
e[n + a[i] + 1].push_back({i, 0});
}
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
e[u].push_back({v, 1});
}
for (int i = 0; i <= mx; i++) {
for (int j = 0; j < M; j++) {
if ((i & (1 << j))) {
e[n + i + 1].push_back({n + (i ^ (1 << j)) + 1, 0});
}
}
}
bfs();
return 0;
}
T2
非常好的一道题,首先我们需要对题面进行一些转化,这个切牌操作太难处理了,考虑把它改成这样:先对每段 reverse,再全局 reverse 一遍。比如切成 \(A + B, C + D, E + F\), 就先变成 \(B + A, D + C, F + E\), 再变成 \(E + F, C + D, A + B\)。然后发现操作是有交换律的,所以如果是偶数根本不需要管全局 reverse,奇数时最后 reverse 一次即可,并且如果进行到第偶数次操作,将操作翻转再输出。
然后 \(\mathcal{O}(n)\) 次操作就很简单了,每次把最小的换前面来即可,这个有点类似选择排序,启发我们使用更快的排序算法。
那么有两个思路:归并和快排,归并这个不太好做,考虑模拟快排,每次通过操作把比 \(mid\) 小的放左边,比 \(mid\) 大的放右边,这样不会被卡,再左右递归即可。
考虑怎么操作,首先一个经典 trick 是把 \(> mid\) 的看成 \(1\), 否则看成 \(0\), 这样的话转化为了 \(0\) 全部放到左边。
考虑对极长的 \(01\) 相同的段压缩成一个 \(0\) 或 \(1\) 并钦定开头有个 \(0\), 那么就有 \(010101...\), 然后将每个 \(010\) 当成一组,翻转 \(01\), 每次会使极长的 \(01\) 相同的段数量除以 \(3\), 所以操作次数是 \(\log_3 n\), 分治一下就是求和,注意到下方的操作其实是可以同时进行的。时间复杂度能过,操作次数不超过 \(72\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 10, M = 72;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
#ifdef ONLINE_JUDGE
return ;
#endif
cout << arg << ' ';
dbg(args...);
}
int n, a[N], len[N];
vector<int>ans[M + 1];
void solve(int id, int l, int r) {
bool ok = 1; for (int i = l + 1; i <= r; i++) if (a[i - 1] > a[i]) { ok = 0; break; }
if (ok) { while (id <= M) { for (int i = l; i <= r; i++) ans[id].push_back(1); id++; } return ; }
int mid = (l + r) >> 1, tot = 0;
while (id <= M) {
tot = 0;
for (int i = l, d = 0; i <= r; d ^= 1) {
len[++tot] = 0;
while (i <= r && (a[i] > mid) == d) i++, len[tot]++;
}
if (tot == 2) break;
int now = l;
for (int i = 1; i <= tot; i++) {
if (i % 3 == 2 && i < tot) {
reverse(a + now, a + now + len[i] + len[i + 1]);
ans[id].push_back(len[i] + len[i + 1]);
} else if (i % 3 && len[i]) {
reverse(a + now, a + now + len[i]);
ans[id].push_back(len[i]);
}
now += len[i];
}
id++;
}
solve(id, l, mid); solve(id, mid + 1, r);
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
solve(1, 1, n);
cout << M << '\n';
for (int i = 1; i <= M; i++) {
if (!(i & 1)) reverse(ans[i].begin(), ans[i].end());
cout << (int)ans[i].size() << '\n';
for (auto x : ans[i]) cout << x << ' ';
cout << '\n';
}
return 0;
}
T3
这题也非常好。题解我会录制成视频。现在先鸽着。
T4
非常好的题。
首先考虑一个性质:如果一条边在一个子图中已经不是 MST 的边了,那它肯定也不是全局 MST 的边。这个性质告诉我们无论如何两个版本之间的图有用的边只有 \(\mathcal{O}(n)\) 条(具体的,是 \(2n-1\))。
接下来考虑 \(\mathcal{O}(nl\alpha(n))\), 首先 \(w\) 只有 \(30\) 桶排肯定没问题,不用带 \(\log\), 然后考虑版本 \(i\) 和版本 \(i + 1\), 我们记版本 \(i - 1\) 和版本 \(i\) 的所有“连边信息”:三元组 \((w, u, v)\) 表示连的边权为 \(w\), 连接了集合 \(u, v\), 然后将这个也当成边参与到版本 \(i\) 和 \(i + 1\) 的 Kruskal 中,如果 \(w\) 之前 \(u, v\) 已经连通,这条边就不要了。注意需要并查集,有较大的常数。
这个做法和正解关系不大,但是和 \(w\) 关系也不大,\(w\) 很大时优于正解。
注意到 \(n\) 轮之后一定会收敛成等差数列,所以 \(l\) 变成了 \(\mathcal{O}(n)\) 级别,不过这题的正解不需要这个性质。
考虑正解,我们只需要知道每个图的 MST 里有多少个 \(\le 1\) 的边,\(\le 2\) 的边,\(\cdots \le 30\) 的边即可。
对于每个 \(0 \le k < 30\) 保留图中 \(\le k\) 的边,跑一个类似连通性的东西:不断加边,不能构成环,假设最后成功加入了 \(t\) 条边,那么边权 \(> k\) 的边就用了 \(|V| - 1 - t\) 条,对于边权为 \(w\) 的边,正好被算了 \(w\) 次,对于 \(G_i\), \(|V| = (i + 1) \times n\), 所以我们只要求出 \(t\) 即可,无需考虑边权问题。
对于前两层的点,并查集暴力做即可,对于后面的点,我们考虑扩展域,前一层的点为 \(u\) 后一层为 \(u + n\), 由于在第二层连通的点对第三层依然连通,所以并查集不需要改变。
考虑第三层相比前一层增加的点对一定是前一层增加的点对 + 边得到的,所以对于新得到的点对 \((u, v)\),如果原先已经连通,就让 \(t \leftarrow t - 1\),因为有一条边可以不用,否则把 \((u, v)\) 当一条边加入即可。由于并查集最多合并 \(n - 1\) 次,所以边数始终是 \(\mathcal{O}(n + m)\)的。然后我们就可以求出每次成功加入的边数的增量的增量,即二阶差分,跑两遍前缀和即可。
时间复杂度 \(\mathcal{O}((n + m + l)V\alpha(n))\), \(V\) 为边权。
点击查看代码
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 2e5 + 10, M = 1e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
struct Edge {
int u, v, w;
bool operator < (const Edge &A) const {
return w < A.w;
}
} e[N];
int n, m, tot, fa[N];
ll ans[M], s[M];
vector<pii>A, B;
int find(int u) { return u == fa[u] ? u : fa[u] = find(fa[u]); }
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> tot;
for (int i = 1, u, v, w; i <= tot; i++) {
cin >> u >> v >> w;
e[i] = {u, v + n, w};
}
sort(e + 1, e + tot + 1);
for (int w = 0; w < 30; w++) {
memset(s, 0, sizeof s);
for (int i = 1; i <= n * 2; i++) fa[i] = i;
A.clear();
for (int i = 1; i <= tot; i++) {
auto [u, v, _] = e[i]; if (_ >w) break;
u = find(u); v = find(v);
if (u < v) swap(u, v);
if (u != v) {
s[1]++; fa[v] = u;
if (u > n && v > n) A.push_back({u - n, v - n}); // 第三层新增的边
}
}
for (int d = 2; ; d++) {
B = A; A.clear();
for (auto [u, v] : B) {
u = find(u); v = find(v);
if (u < v) swap(u, v);
if (u != v) {
fa[v] = u;
if (u > n && v > n) A.push_back({u - n, v - n});
} else s[d]--;
}
if (A.empty()) break;
}
for (int i = 1; i <= m; i++) s[i] += s[i - 1];
for (int i = 1; i <= m; i++) s[i] += s[i - 1];
for (int i = 1; i <= m; i++) {
ans[i] += (ll)(i + 1) * n - 1 - s[i];
}
}
for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
return 0;
}
DMY DAY6'
T1
宝宝题,看代码吧。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
typedef long long ll;
typedef pair<int, int> pii;
int T, n, k, sz[N], f[N], ans; //f[i]: 以 i 为根的子树中删一条经过 i 的路径的答案最大值
// (经过 i 指一个子树中的点连向 i 并可以向上延伸, 也可以选择让 i 为路径的一个端点并且路径向上延伸)
vector<int>e[N];
inline void dfs(int u, int fa) {
sz[u] = 1; f[u] = -1e9;
int sum = 0, mx = 0, nmx = -1e9;
for (auto v : e[u]) if (v != fa) {
dfs(v, u);
sz[u] += sz[v];
sum += (sz[v] >= k);
}
for (auto v : e[u]) if (v != fa) {
int x = f[v] - (sz[v] >= k);
if (x > mx) nmx = mx, mx = x;
else nmx = max(nmx, x);
f[u] = max(f[u], f[v] + sum - (sz[v] >= k));
}
ans = max(ans, mx + nmx + sum + (n - sz[u] >= k)); //mx + nmx + sum: 一条完全包含在 u 子树中, 答案最大的路径
// n - sz[u]: u 子树的外面
f[u] = max(f[u], sum);
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> T;
while (T--) {
cin >> n >> k;
ans = 0;
for (int i = 1; i <= n; i++) e[i].clear();
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
e[x].push_back(y), e[y].push_back(x);
}
dfs(1, 0);
cout << ans << '\n';
}
return 0;
}
T2
考虑得到 \(L-th\) 之后类似超级钢琴,用堆维护即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll> pll;
const int N = 3e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
ll n, a[N], pre[N], nxt[N], stk[N], top, L, R, len[N];
ll check(ll x) {
ll res = 0; ll ans = 0;
for (ll i = 1; i <= n; i++) {
ll l = (x + 1 + a[i] - 1) / a[i];
if (l <= nxt[i] - pre[i] + 1) {
ll up = i - pre[i], down = max(0ll, l + i - nxt[i] - 1);
if (up >= l) res += (ll)(i - l - pre[i] + 1) * (nxt[i] - i + 1);
up = min(up, l - 1);
if (up < down) continue;
res += (up - down + 1) * (nxt[i] - i - l + 2) + (up - down + 1) * (up + down) / 2;
}
}
return (ll)n * (n + 1) / 2 - res;
}
ll calc(int x) {
ll l = 0, r = (ll)3e14 + 1;
while (l + 1 < r) {
ll mid = (l + r) >> 1;
if (check(mid) >= L) r = mid;
else l = mid;
}
return r;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) cin >> a[i], pre[i] = 1, nxt[i] = n;
for (ll i = 1; i <= n; i++) {
while (top && a[stk[top]] > a[i]) nxt[stk[top--]] = i - 1;
stk[++top] = i;
}
top = 0;
for (ll i = n; i; i--) {
while (top && a[stk[top]] >= a[i]) pre[stk[top--]] = i + 1;
stk[++top] = i;
}
top = 0;
cin >> L >> R;
ll x = calc(L), num = check(x);
// if (a[1] == 1) dbg("DS", x, num, L, R);
for (ll i = L; i <= R && i <= num; i++) cout << x << ' ';
R -= min(R, num);
priority_queue<pll, vector<pll>, greater<pll>>q;
for (ll i = 1; i <= n; i++) {
len[i] = (x + 1 + a[i] - 1) / a[i];
if (len[i] <= nxt[i] - pre[i] + 1) q.push({(ll)a[i] * len[i], i});
}
while (!q.empty() && R) {
auto [val, i] = q.top(); q.pop();
ll cnt = min(R, 1ll + min(i, nxt[i] - len[i] + 1) - max(pre[i], i - len[i] + 1));
R -= cnt;
while (cnt--) {
cout << val << ' ';
}
len[i]++;
if (len[i] <= nxt[i] - pre[i] + 1) q.push({(ll)a[i] * len[i], i});
}
return 0;
}
T3
首先考虑容斥。假设有 \(k\) 个位置违反了条件,方案数为 \(f_k\), 那么贡献就是
接着考虑计算 \(f_k\)。
考虑图论建模,\(a_i \leftrightarrow b_i\) 连边,会形成若干个环,那么 \(f_k\) 就是总共选 \(k\) 条边选的边都是独立集的方案数。对于环上的独立集。
dp 不是很好做,看看有没有组合意义。对于一条 \(n\) 个点的链,选 \(k\) 个点组成独立集的方案数是多少,这个可以想成插板,剩下的 \(n - k\) 个元素有 \(n - k + 1\) 个空,要插入 \(k\) 个数,所以是 \(\dbinom{n - k + 1}{k}\) 种,放在环上,将每条边 \((u, v)\) 拆成两个点,第一个点表示选 \(u\), 第二个表示选 \(v\), 一共 \(2n\) 个点,就变成了 \(2n\) 个点的环选独立集,断环成链后就同理了。最后背包一下即可。
注意 AT 的原有自环的情况,令 \(f_i \leftarrow f_i + f_{i - 1}\) 即可。
时间复杂度 \(\mathcal{O}(n^2)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int N = 1e3 + 10, mod = 1e9 + 7;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
#ifdef ONLINE_JUDGE
return ;
#endif
cout << arg << ' ';
dbg(args...);
}
int T, n, m, a[N], b[N], vis[N];
ll fac[N << 1], ifac[N << 1], f[N], g[N];
ll qpow(ll a, int b) {
ll res = 1;
for (; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod;
return res;
}
ll C(int n, int m) {
if (n < m || n < 0 || m < 0) return 0;
return fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> T;
while (T--) {
memset(f, 0, sizeof f); memset(vis, 0, sizeof vis);
cin >> n; m = n << 1 | 1;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[a[i]];
fac[0] = ifac[0] = 1;
for (int i = 1; i <= m; i++) fac[i] = fac[i - 1] * i % mod; ifac[m] = qpow(fac[m], mod - 2);
for (int i = m; i; i--) ifac[i - 1] = ifac[i] * i % mod;
int sum = 0;
f[0] = 1;
for (int i = 1; i <= n; i++) if (!vis[i]) {
int tot = 0;
for (int j = i; !vis[j]; j = b[j]) tot++, vis[j] = 1;
if (tot == 1) {
for (int j = sum + 1; j; j--) f[j] = (f[j] + f[j - 1]) % mod;
} else {
memset(g, 0, sizeof g);
for (int j = sum; j >= 0; j--) for (int k = tot; k >= 0; k--) {
g[j + k] = (g[j + k] + f[j] * (C(tot * 2 - k, k) + C(tot * 2 - k - 1, k - 1)) % mod) % mod;
}
memcpy(f, g, sizeof f);
}
sum += tot;
}
ll ans = 0;
for (int i = 0; i <= n; i++) {
ll sgn = (i & 1) ? mod - 1 : 1;
ans = (ans + sgn * fac[n - i] % mod * f[i] % mod) % mod;
}
cout << ans << '\n';
}
return 0;
}
DMY DAY7'
T1
条件很强,根据 \(x + y = x \lor y + x \land y\) 得到 \(x_1 \land x_2 \cdots \land x_n = 0\), 然后直接数位 dp 即可。
时间复杂度 \(\mathcal{O}(n2^n\log V)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 16, mod = 998244353;
typedef long long ll;
typedef pair<int, int> pii;
int n, dp[65][1 << N];
ll r[N];
void add(int &x, int y) {
x += y;
if (x >= mod) x -= mod;
}
int dfs(int x, int lim) { //x 表示当前是哪一位, lim 是一个2进制状态, 第 i 位表示目前选的 i 是否顶上界
if (x == -1) return 1;
if (dp[x][lim] != -1) return dp[x][lim];
int res = 0;
// 所有数这一位都填0
int nxt = lim;
for (int i = 0; i < n; i++) {
if (lim & (1 << i) && r[i] & (1ll << x)) nxt ^= (1 << i);
}
add(res, dfs(x - 1, nxt));
//选一个数给这个数的第 x 位填1
for (int i = 0; i < n; i++) {
if (lim & (1 << i) && !(r[i] & (1ll << x))) continue;
add(res, dfs(x - 1, nxt | (lim & (1 << i))));
}
return dp[x][lim] = res;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> r[i];
}
memset(dp, 255, sizeof dp);
cout << dfs(62, (1 << n) - 1) << '\n';
return 0;
}

浙公网安备 33010602011771号