## 密码
#枚举 #字典树
首先密码中的每一位要求属于该位的尝试列表,否则永远试不出正确代码。然后对于 $k$ 个已经试过的密码,先给它们去重,然后判断是否每一位均属于尝试列表,如果是就对答案减一即可。时间复杂度 $\mathcal O(nk)$。
```cpp
#include <fstream>
#include <unordered_set>
#include <string>
using namespace std;
using LL = long long;
const LL kMaxN = 1e4 + 5;
ifstream cin("password.in");
ofstream cout("password.out");
LL n, k, sum = 1;
string s[kMaxN], a, t;
unordered_set<string> st;
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> k >> a;
a = " " + a;
for (LL i = 1, x; i <= n; i++) {
cin >> x >> s[i];
sum *= x;
if (s[i].find(a[i]) == string::npos) {
cout << "-1\n";
return 0;
}
}
for (LL i = 1; i <= k; i++) {
cin >> t;
t = " " + t;
if (st.count(t)) {
continue;
}
st.insert(t);
bool b = 1;
for (LL j = 1; j <= n && b; j++) {
b &= s[j].find(t[j]) != string::npos;
}
if (b) {
sum--;
}
}
cout << sum << '\n';
return 0;
}
```
## 矩阵游戏
#等比数列求和 #快速幂 #乘法逆元
令 $f(x)=ax+b,g(x)=cx+d$,若每一行的第一个元素为 $x$(第一行为 $1$)那么每一行的最后一个元素等于 $f^{m-1}(x)$(迭代 $m-1$ 次),而下一行的第一个元素为 $g(f^{m-1}(x))$。对于同一个一次函数的嵌套 $f^n(x)$,暴力拆开得到等于
$
a^n+b\sum_{i=0}^{n-1}a^i
$
通过等比数列求和公式计算。算出 ${} g(f^{m-1}(x)) {}$ 的系数之后再对其进行 $n-1$ 次迭代,得到第 $n$ 行的第一个元素,然后再带入 $f(x)$ 进行 $m-1$ 次迭代即可。
注意本题 $\log(n/m)$ 达到 $10^6$,所以直接跑十进制的快速幂即可。等比数列求和注意系数为一的特殊情况。
```cpp
#include <fstream>
#include <utility>
#include <string>
using namespace std;
using LL = long long;
const LL kMod = 1e9 + 7;
ifstream cin("matrix.in");
ofstream cout("matrix.out");
LL a, b, c, d;
string n, m;
LL pow(LL a, LL b) {
LL res = 1;
for (; b; b /= 2) {
b % 2 && (res = res * a % kMod);
a = a * a % kMod;
}
return res;
}
LL pow(LL a, string n) {
LL b = a, res = 1;
for (; n.size(); n.pop_back()) {
if (n.back() != '0') {
res = res * pow(b, n.back() - '0') % kMod;
}
b = pow(b, 10);
}
return res;
}
LL num(const string &n) {
LL res = 0;
for (char c : n) {
res = (res * 10 + c - '0') % kMod;
}
return res;
}
pair<LL, LL> calc(const string &n, LL a, LL b) {
if (a == 1) {
return {1, num(n) * b % kMod};
}
return {pow(a, n), (pow(a, n) - 1 + kMod) % kMod * pow(a - 1, kMod - 2) % kMod * b % kMod};
}
void sub(string &s) {
s.back()--;
for (LL i = s.size() - 1; i >= 0; i--) {
if (s[i] < '0') {
s[i] += 10, s[i - 1]--;
}
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> a >> b >> c >> d;
sub(n), sub(m);
auto p1 = calc(m, a, b);
auto p2 = calc(n, p1.first * c % kMod, (p1.second * c % kMod + d) % kMod);
cout << (p1.first * (p2.first + p2.second) % kMod + p1.second) % kMod << '\n';
return 0;
}
```
## 兴趣排列
#数学
首先可以简单地写出 $\mathcal O(n^2)$ 的 DP。
先判无解,对于 $a_1\ne 0$ 是无解的,$a_i<a_{i-1}$ 也是,其次有 $a_i<n$。
设 $[l,r]$ 表示前 $i$ 个元素的值域区间,那么有 $r-l=i$。如果 $a_i=a_{i-1}$,那么对于任意 $[l,r]$ 区间都不变,只需要在区间内选择一个没选过的即可;否则对于 $[l,r]$ 均可以像两边的任意一边拓展。
所以只需要先让 $ans\gets 1$,然后对于 $i\ge 2$,有
$
ans\gets\begin{cases}
ans\times [(a_i+1)-(i-1)] & \text{if}~a_i=a_{i-1} \\
ans\times 2 & \text{if}~a_i>a_{i-1}
\end{cases}
$
时间复杂度 $\mathcal O(n)$。
```cpp
#include <fstream>
using namespace std;
using LL = long long;
const LL kMaxN = 2e6 + 5, kMod = 1e9 + 7;
ifstream cin("perm.in");
ofstream cout("perm.out");
LL a[kMaxN], dp[kMaxN], f[kMaxN], t, n;
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n;
for (LL i = 1; i <= n; i++) {
cin >> a[i];
}
if (a[1]) {
cout << "0\n";
continue;
}
bool b = 1;
for (LL i = 2; i <= n && b; i++) {
b &= a[i] >= a[i - 1];
b &= a[i] <= n - 1;
}
if (!b) {
cout << "0\n";
continue;
}
LL ans = 1;
for (LL i = 2; i <= n; i++) {
if (a[i] == a[i - 1]) {
ans = ((a[i] + 1) - (i - 1) + kMod) % kMod * ans % kMod;
} else {
ans = ans * 2 % kMod;
}
}
cout << ans << '\n';
}
return 0;
}
```
## 交换礼物
#离散化 #序列dp
首先考虑对于一个已有的序列,如何计算最多能让多少位选手开心,等价于最小化不开心的选手数量。不难发现若其出现次数最多的数的出现次数 $x$ 小于等于 $n/2$ 时,每个选手按照手上礼物编号排一排再递给往后的 $x$ 个人,那么不会有人礼物编号重复;否则若其它类型的礼物数量为 $y=n-x$,那么会有 $x-y$ 个人不开心。
所以我们并不需要构造最终的序列,只需要搞清楚哪种礼物有多少人拥有即可。这是典型的计数问题。设 $dp_i$ 表示第 $i$ 个序列需要计入到最终序列 $dp_i$ 次,那么首先 $dp_n$ 为 $1$。从前往后按照下标拓扑序遍历,然后对于所有由两个序列拼起来的序列,让 $dp_x\gets dp_x+dp_i,dp_y\gets dp_y+dp_i$。最后统计到 map(也可以像我一样离散化),求出众数和总数即可。
时间复杂度 $\mathcal O(n+k\log_2 k)$。
```cpp
#include <fstream>
#include <algorithm>
#include <vector>
using namespace std;
using LL = long long;
const LL kMaxN = 1e6 + 5;
ifstream cin("gifts.in");
ofstream cout("gifts.out");
struct Node {
LL o, x, y;
vector<LL> v;
} a[kMaxN];
LL dp[kMaxN], f[kMaxN], val[kMaxN], t, n, m;
int main() {
cin.tie(0)->sync_with_stdio(0);
for (cin >> t; t; t--) {
cin >> n;
m = 0;
for (LL i = 1, k; i <= n; i++) {
cin >> a[i].o;
if (a[i].o == 1) {
cin >> k;
a[i].v.resize(k);
for (LL &j : a[i].v) {
cin >> j;
val[++m] = j;
}
} else {
cin >> a[i].x >> a[i].y;
a[i].v.clear();
}
}
sort(val + 1, val + m + 1);
m = unique(val + 1, val + m + 1) - val - 1;
fill(f + 1, f + m + 1, 0);
fill(dp + 1, dp + n + 1, 0);
dp[n] = 1;
for (LL i = n; i >= 1; i--) {
if (a[i].o == 2) {
dp[a[i].x] += dp[i];
dp[a[i].y] += dp[i];
}
}
for (LL i = 1; i <= n; i++) {
if (a[i].o == 1) {
for (LL j : a[i].v) {
f[lower_bound(val + 1, val + m + 1, j) - val] += dp[i];
}
}
}
LL sum = 0, mx = 0;
for (LL i = 1; i <= m; i++) {
sum += f[i];
mx = max(mx, f[i]);
}
LL other = sum - mx;
if (mx <= other) {
cout << sum << '\n';
} else {
cout << sum - (mx - other) << '\n';
}
}
return 0;
}
```