T1. 数学作业
发现 \(a-b\) 太大会很快超过题目所限的范围,所以 \(a-b\) 值并不大。
然后枚举差值 \(d\),发现 \(\frac{(x+d)!}{x!}\) 关于 \(x\) 单调递增。所以可以二分判断存不存在 \(x\) 满足 \(\frac{(x+d)!}{x!} = n\)。
注意特判 \(n=1\) 。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using l3 = __int128;
void solve() {
ll n;
cin >> n;
if (n == 1) {
puts("inf");
return;
}
int ans = 0;
l3 prod = 1;
for (int d = 1;; ++d) {
prod *= d+1;
if (prod > n) break;
ll ac = 0, wa = n;
while (abs(ac-wa) > 1) {
ll wj = (ac+wa)/2;
bool ok = true;
l3 fact = 1;
for (int i = 1; i <= d; ++i) {
fact *= wj+i;
if (fact > n) {
ok = false;
break;
}
}
if (fact == n) {
ans++;
break;
}
if (ok) ac = wj; else wa = wj;
}
}
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
T2. 回文串
对于一个长为 \(M\) 的字符串 \(S\),它是回文当且仅当对于任意的 \(1 \leqslant i \leqslant M\) 都有 \(S_i = S_{M-i+1}\) 。
特别地,要使这 \(N\) 个字符串都成为回文串,需要满足:
- 令 \(C_i\) 表示由所有字符串的第 \(i\) 个字符组成的字符串,也就是 \(C_i = S_{1,i}S_{2,i} \ldots S_{N,i}\),\(C_i\) 本质上相当于这些字符串的第 \(i\) 列。
- 那么对于每个 \(1 \leqslant i \leqslant M\),都必须有 \(C_i = C_{M+1-i}\) 。
注意到给定的操作只允许我们在同一列内移动字符 —— 无法将其移动到其他列。
所以,我们只需计算以下内容:
- 对于每个 \(1 \leqslant i \leqslant M\),在允许对两串都进行相邻交换的情况下,求出将 \(C_i\) 变为 \(C_{M+1-i}\) 的最小相邻交换次数。
- 然后,把所有列对的答案加起来
- 如果有任何列对无法相等,则答案为 \(-1\)。
因此,我们只剩下一个子问题:给定两个字符串 \(S\) 和 \(T\):给定两个字符串 \(S\) 和 \(T\),在允许对两串都进行相邻交换的情况下,求使 \(S=T\) 所需的最少操作次数。
这可以通过一个有趣的观察来解决:只在一个字符串上执行交换,而不改变另一个字符串就足够了!
证明:
假设我们对 \(S\) 和 \(T\) 都进行了相邻交换,并且最终它们相等。令 \(T\) 上的最后一次交换为 \((i, i+1)\) 。
然后,我们可以不在 \(T\) 上执行这次交换,而是在 \(S\) 上将其作为最后一次操作。这样依然保持 \(S=T\),同时在 \(T\) 上少执行一次操作。
反复进行这个过程将使我们达到 \(S=T\) 这个状态,但没有按要求对 \(T\) 进行任何操作。
因此,我们保持 \(T\) 不变,计算将 \(S\) 变为 \(T\) 所需的最小相邻交换次数。
这是一个相当经典的问题。
基本思路如下:
- 对每个位置 \(i\),计算 \(S_i\) 应该去到的位置,记为 \(pos_i\) 。
- 可以按以下方式来做:
- 逐字符处理
- \(S\) 中第 \(1\) 次出现的
a应去到 \(T\) 中第 \(1\) 次出现的a的位置,\(S\) 中第 \(2\) 次出现的a应去到 \(T\) 中第 \(2\) 次出现的a的位置,以此类推。 - 对每个字符都这样处理。
- 只需知道 \(S\) 和 \(T\) 中各字符的位置,即可直接建立映射关系。
- 一旦我们知道每个字符最终的位置,问题本质上要求达到此配置的最小交换次数。
- 这实际上就是计算数组 \(pos\) 的逆序数,可通过多种方法在 \(O(N\log N)\) 时间复杂度内高效求解。
代码实现
#include <bits/extc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using namespace __gnu_pbds;
using ll = long long;
template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
ll f(vector<int>& a) {
ll res = 0;
Tree<int> t;
for (int x : a) {
res += t.size() - t.order_of_key(x);
t.insert(x);
}
return res;
}
ll calc(string s, string t) {
int n = s.size();
vector<vector<int>> pos(26);
rep(i, n) pos[s[i]-'a'].push_back(i);
vector<int> ptr(26), reach(n);
rep(i, n) {
int c = t[i]-'a';
if (ptr[c] == pos[c].size()) return -1;
reach[i] = pos[c][ptr[c]];
++ptr[c];
}
return f(reach);
}
void solve() {
int n, m;
cin >> n >> m;
vector<string> s(n);
rep(i, n) cin >> s[i];
ll ans = 0;
rep(j, m-1-j) {
string a, b;
rep(i, n) {
a += s[i][j];
b += s[i][m-1-j];
}
ll now = calc(a, b);
if (now == -1) {
ans = -1;
break;
}
ans += now;
}
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
T3. 记忆恢复
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
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;
}
struct modinv {
int n; vector<mint> d;
modinv(): n(2), d({0,1}) {}
mint operator()(int i) {
while (n <= i) d.push_back(-d[mod%n]*(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);
}
int main() {
int n, k;
cin >> n >> k;
vector<int> a(n);
rep(i, n) cin >> a[i], a[i]--;
vector<int> p(n+1);
p[n] = -1;
rep(i, n) p[a[i]] = i;
int cnt = 1;
rep(i, n-1) {
if (p[a[i]+1] > p[a[i+1]+1]) cnt++;
}
k -= cnt;
mint ans = comb(n+k, k);
cout << ans << '\n';
return 0;
}
T4. 大战
考虑倒着构造这个方案。
可以认为摧毁防御塔实际上是将序列中对应的元素置为 \(0\)。
初始时,我们假设每个元素的值均为 \(0\),然后再从后往前给序列填数。为了方便表述,我们设 \(f(i)\) 为序列 \([0, 0, \ldots, a_i, a_{i+1}, \ldots, a_n]\) 的答案,显然有 \(f(n+1)=0\)。
然后考虑如何从 \(f(i+1)\) 推出 \(f(i)\) 。
考虑确定 \(a_i\) 的摧毁时机。我们可以枚举 \(k\),然后假设在 \(p=k\) 时摧毁 \(a_i\) 。此时 \(a_i\) 对答案有以下贡献:
- 在 \(p<k\) 时,每次选择向右移动 \(1\) 格的时候,\(a_i\) 都会被计入伤害,总贡献为 \(+a_i \cdot (k-1)\) 。
- 对于 \(i<j \leqslant k\) 的所有下标 \(j\),如果 \(a_j > a_i\) ,那么 \(a_j\) 会先于 \(a_i\) 被删掉。假设有 \(cnt\) 个这样的下标 \(j\),那么 \(a_i\) 会再被推迟 \(cnt\) 次操作被摧毁,总贡献为 \(+a_i \cdot cnt\) 。
- 对于 \(i<j\) 的所有下标 \(j\),如果 \(a_j \leqslant a_i\) ,那么 \(a_j\) 会后于 \(a_i\) 被删掉。假设这些 \(a_j\) 的和为 \(sum\) ,那么由于摧毁 \(a_i\) 需要花费 \(1\) 次操作,所以这些元素会再晚 \(1\) 次操作被摧毁,总贡献为 \(+sum\) 。
于是有 \(f(i) = f(i+1) + \min_k \{ a_i \cdot (cnt+k-1) + sum\}\) 。\(cnt\) 和 \(sum\) 可以在枚举 \(k\) 时维护。
总复杂度为 \(O(n^2)\) 。
浙公网安备 33010602011771号