T1: 子集和(四)
可以先求出由这 \(n\) 个数组成 \(t\) 的方案数,这是简单的完全背包问题
记 dp[t]
表示组成 \(t\) 的方案数
不选择 \(a_i\) 的前提下组成 \(t\) 的方案数也就是把 \(dp[t]\) 减掉至少包含一个 \(a_i\) 的方案
回想一下:\(dp[t-a_i] \to dp[t]\)
所以每个问题的答案就是 \(dp[t] - dp[t-a_i]\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using std::cin;
using std::cout;
using std::vector;
using std::istream;
using std::ostream;
using ll = long long;
//const int mod = 998244353;
const int mod = 1000000007;
struct mint {
ll x;
mint(ll x=0):x((x%mod+mod)%mod) {}
mint operator-() const {
return mint(-x);
}
mint& operator+=(const mint a) {
if ((x += a.x) >= mod) x -= mod;
return *this;
}
mint& operator-=(const mint a) {
if ((x += mod-a.x) >= mod) x -= mod;
return *this;
}
mint& operator*=(const mint a) {
(x *= a.x) %= mod;
return *this;
}
mint operator+(const mint a) const {
return mint(*this) += a;
}
mint operator-(const mint a) const {
return mint(*this) -= a;
}
mint operator*(const mint a) const {
return mint(*this) *= a;
}
mint pow(ll t) const {
if (!t) return 1;
mint a = pow(t>>1);
a *= a;
if (t&1) a *= *this;
return a;
}
// for prime mod
mint inv() const {
return pow(mod-2);
}
mint& operator/=(const mint a) {
return *this *= a.inv();
}
mint operator/(const mint a) const {
return mint(*this) /= a;
}
};
istream& operator>>(istream& is, mint& a) {
return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
return os << a.x;
}
mint dp[10005];
int main() {
int n, t;
cin >> n >> t;
vector<int> a(n);
rep(i, n) cin >> a[i];
dp[0] = 1;
rep(i, n) {
for (int j = 0; j <= t-a[i]; ++j) {
dp[j+a[i]] += dp[j];
}
}
// 考虑减掉至少包含一个 a[i] 的方案
rep(i, n) {
if (t >= a[i]) {
dp[t] -= dp[t-a[i]];
}
cout << dp[t] << '\n';
if (t >= a[i]) {
dp[t] += dp[t-a[i]];
}
}
return 0;
}
T2:路径问题
可以考虑倍增法
记 jump[i][j]
表示从点 \(i\) 出发,走 \(2^j\) 条边后到达的点
转移式:
记 mx[i][j]
表示从点 \(i\) 出发,经过 \(2^j\) 条边的路径的边权最大值
转移式:
\( mx[i][j] = \max(mx[i][j-1], mx[jump[i][j-1]][j-1]) \)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using std::cin;
using std::cout;
using std::max;
using std::vector;
const int MX = 500005;
int jump[MX][21];
int mx[MX][21];
inline void chmax(int& x, int y) { if (x < y) x = y; }
int main() {
int n, k;
cin >> n >> k;
rep(i, n) cin >> jump[i][0];
rep(i, n) cin >> mx[i][0];
rep(j, 20)rep(i, n) {
jump[i][j] = jump[jump[i][j-1]][j-1];
mx[i][j] = max(mx[i][j-1], mx[jump[i][j-1]][j-1]);
}
rep(i, n) {
int v = i, ans = 0;
for (int j = 0; j <= 20; ++j) {
if (k>>j&1) {
chmax(ans, mx[v][j]);
v = jump[v][j];
}
}
cout << ans << '\n';
}
return 0;
}
T3:航海探险
可以考虑用带权并查集来维护
记 dist[i]
表示点 \(i\) 到其祖先节点的距离
需要将每个点拆成两个点,也就是 \(x\) 方向和 \(y\) 方向
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using std::cin;
using std::cout;
using std::min;
using std::vector;
const int MX = 1e5+5;
int fa[MX];
int dist[MX];
int find(int x) {
if (fa[x] == x) return x;
int fx = find(fa[x]);
dist[x] += dist[fa[x]];
return fa[x] = fx;
}
inline void connect(int a, int b, int d) {
int ra = find(a), rb = find(b);
if (ra == rb) return;
dist[ra] = d-dist[a]+dist[b];
fa[ra] = rb;
}
int main() {
int n, m;
cin >> n >> m;
// i x 方向
// i+n y 方向
rep(i, n*2) fa[i] = i;
while (m--) {
char type;
cin >> type;
switch (type) {
case 'E': {
int a, b, e;
cin >> a >> b >> e;
connect(a, b, e);
connect(a+n, b+n, 0);
break;
}
case 'S': {
int a, b, e;
cin >> a >> b >> e;
connect(a+n, b+n, e);
connect(a, b, 0);
break;
}
case 'W': {
int a, b, e;
cin >> a >> b >> e;
connect(b, a, e);
connect(a+n, b+n, 0);
break;
}
case 'N': {
int a, b, e;
cin >> a >> b >> e;
connect(b+n, a+n, e);
connect(a, b, 0);
break;
}
case '?': {
int a, b;
cin >> a >> b;
if (find(a) == find(b) and find(a+n) == find(b+n)) {
cout << abs(dist[a]-dist[b])+abs(dist[a+n]-dist[b+n]) << '\n';
}
else puts("?");
break;
}
}
}
return 0;
}
T4 : 没有考试的天数(二)
将 \(1 \sim t\) 中 \(a_i (1 \leqslant i \leqslant n)\) 的倍数都去除便是答案
可以考虑容斥原理:
\( \begin{aligned} |\bigcup_{i = 1}^{n} A_i| & = |A_1 \cup A_2 \cup \cdots A_n| \\ &= \sum_{i = 1}^n |A_i| - \sum_{1 \leqslant i < j \leqslant n} |A_i \cap A_j|+ \sum_{1 \leqslant i < j < k \leqslant n} |A_i \cap A_j \cap A_k| + \cdots + (-1)^{n-1} |A_1 \cap A_2 \cap \cdots \cap A_n |\\ &= \sum_{k=1}^{n} (-1)^{k-1} \sum_{1 \leqslant i_1 < i_2 < \cdots < i_k \leqslant n} |A_{i_1} \cap A_{i_2} \cap \cdots \cap A_{i_k}| \end{aligned} \)
\(1 \sim t\) 中是 \(a_i\) 的倍数的数有 \(\lfloor\frac{t}{a_i}\rfloor\) 个
\(1 \sim t\) 中既是 \(a_i\) 的倍数又是 \(a_j\) 的倍数的数有 \(\lfloor\frac{t}{\operatorname{lcm}(a_i\, , \, a_j)}\rfloor\) 个
\(\quad \vdots\)
\(1 \sim t\) 中是 \(\{a_{x_1}, a_{x_2}, \cdots, a_{x_k}\}\) 中每个数的倍数的数有 \(\lfloor\frac{t}{\operatorname{lcm}(a_{x_1}\, , \, a_{x_2}\, , \,\cdots\, , \, a_{x_k})}\rfloor\) 个
代码实现
#pragma GCC optimize ("O2")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target ("avx2"
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using std::cin;
using std::cout;
using std::lcm;
using std::vector;
using ll = long long;
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n; ll t;
cin >> n >> t;
vector<ll> a(n);
rep(i, n) cin >> a[i];
ll ans = t;
for (int i = 1; i < 1<<n; ++i) {
ll l = 1;
rep(j, n) if (i>>j&1) {
if (lcm(l, a[j]) > t) {
l = -1;
break;
}
l = lcm(l, a[j]);
}
if (l != -1) { // 注意一定要特判,不然会超时
if (__builtin_parity(i)) ans -= t/l;
else ans += t/l;
}
}
cout << ans << '\n';
return 0;
}