今天这几题咋就那么毒瘤呢?
A – String
其实这道题考场上应该能做出来的,是一道很简单的计数问题。在考场上一看到字符串就懵了,不知道为什么我对字符串的字典序有阴影,现在看来就是一道傻逼题。
考虑这样计数:
- 枚举一个\(i\),考虑前\(i-1\)个字符与\(T\)串相同,然后第\(i\)个字符小于\(T[i]\),这样可以保证后面怎么放置字母都能满足要求。
- 在枚举了\(i\)的情况下,枚举字符\(ch\),范围在\([a, T[i])\),考虑以下两种情况对答案的贡献:
- 如果\(ch = S[i]\),那么后面需要变动的字符个数为\(k\)个,对答案的贡献:\[ {n – i \choose k} 25^{k} \]
- 如果不等于,那么后面需要变动的字符个数为\(k-1\)个,因为本位占了一个;对答案的贡献:\[ {n – i \choose k – 1} 25^{k – 1} \]
- 贡献之后,如果本位\(T[i] \neq S[i]\),那么把\(k–\),代表多固定了一个不同的字符。
const int MAX_N = 1e5 + 2000, mod = 1e9 + 7;
ll n, k, level[MAX_N], level_inv[MAX_N], ans;
ll quick_pow(ll bas, ll tim)
ll comb(ll n_, ll k_) { return level[n_] * level_inv[n_ - k_] % mod * level_inv[k_] % mod; }
scanf("%lld%lld", &n, &k), level[0] = 1;
for (int i = 1; i <= n; i++)
level[i] = level[i - 1] * i % mod;
level_inv[n] = quick_pow(level[n], mod - 2);
for (int i = n - 1; i >= 1; i--)
level_inv[i] = level_inv[i + 1] * (i + 1) % mod;
scanf("%s", S + 1), scanf("%s", T + 1);
for (int i = 1; i <= n; i++)
for (int ch = 0; ch < T[i] - 'a'; ch++)
(ans += comb(n - i, k) * quick_pow(25, k) % mod) %= mod;
(ans += comb(n - i, k - 1) * quick_pow(25, k - 1) % mod) %= mod;
printf("%lld", (ans + 1) % mod);
// string.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 1e5 + 2000, mod = 1e9 + 7;
ll n, k, level[MAX_N], level_inv[MAX_N], ans;
char S[MAX_N], T[MAX_N];
ll quick_pow(ll bas, ll tim)
{
if (tim < 0)
return 0;
ll ans = 1;
while (tim)
{
if (tim & 1)
ans = ans * bas % mod;
bas = bas * bas % mod;
tim >>= 1;
}
return ans;
}
ll comb(ll n_, ll k_) { return level[n_] * level_inv[n_ - k_] % mod * level_inv[k_] % mod; }
int main()
{
scanf("%lld%lld", &n, &k), level[0] = 1;
for (int i = 1; i <= n; i++)
level[i] = level[i - 1] * i % mod;
level_inv[n] = quick_pow(level[n], mod - 2);
for (int i = n - 1; i >= 1; i--)
level_inv[i] = level_inv[i + 1] * (i + 1) % mod;
level_inv[0] = 1;
scanf("%s", S + 1), scanf("%s", T + 1);
for (int i = 1; i <= n; i++)
{
for (int ch = 0; ch < T[i] - 'a'; ch++)
{
if (ch + 'a' == S[i])
(ans += comb(n - i, k) * quick_pow(25, k) % mod) %= mod;
else
(ans += comb(n - i, k - 1) * quick_pow(25, k - 1) % mod) %= mod;
}
if (S[i] != T[i])
k--;
}
printf("%lld", (ans + 1) % mod);
return 0;
}
// string.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 1e5 + 2000, mod = 1e9 + 7;
ll n, k, level[MAX_N], level_inv[MAX_N], ans;
char S[MAX_N], T[MAX_N];
ll quick_pow(ll bas, ll tim)
{
if (tim < 0)
return 0;
ll ans = 1;
while (tim)
{
if (tim & 1)
ans = ans * bas % mod;
bas = bas * bas % mod;
tim >>= 1;
}
return ans;
}
ll comb(ll n_, ll k_) { return level[n_] * level_inv[n_ - k_] % mod * level_inv[k_] % mod; }
int main()
{
scanf("%lld%lld", &n, &k), level[0] = 1;
for (int i = 1; i <= n; i++)
level[i] = level[i - 1] * i % mod;
level_inv[n] = quick_pow(level[n], mod - 2);
for (int i = n - 1; i >= 1; i--)
level_inv[i] = level_inv[i + 1] * (i + 1) % mod;
level_inv[0] = 1;
scanf("%s", S + 1), scanf("%s", T + 1);
for (int i = 1; i <= n; i++)
{
for (int ch = 0; ch < T[i] - 'a'; ch++)
{
if (ch + 'a' == S[i])
(ans += comb(n - i, k) * quick_pow(25, k) % mod) %= mod;
else
(ans += comb(n - i, k - 1) * quick_pow(25, k - 1) % mod) %= mod;
}
if (S[i] != T[i])
k--;
}
printf("%lld", (ans + 1) % mod);
return 0;
}
B – Running
这道题是一道好题,我对数论中循环的东西目前了解的很少,现在正好来了解一下。
手玩样例可以发现规律:对于一个步数为\(step_i\)的人,它可以到的格子的编号一定是\(gcd(step_i, n)\)的倍数。利用这个性质,我们可以枚举\(n\)的因数\(d\),然后看能不能找到一个\(gcd(a_i, n)|d\),如果找到了,这意味着\(d\)的倍数会被标记。之后,对答案加上\(\varphi(\frac{n}{d})\)。
但为什么\(\varphi\)可以保证计数不重复呢?这个问题一开始也困扰了我,但是之后突然顿悟:考虑一种重复的情况\(d, k_1 d, k_2 d, k_3 d \dots\),列式:
\[ gcd(a_x, n) | d \\ gcd(a_x, n) | k_1 d \\ gcd(a_x, n) | k_2 d \\ \dots \]
现在推出:
\[ \sum_{i = 1}^r \varphi(\frac{n}{k_i d}), k_i d | n \\ \sum_{x|\frac{n}{d}} \varphi(\frac{n}{x}) = \frac{n}{d} \]
其中\(d\)为最小的、合法的(\( \exists i, gcd(a_i, n) | d \))因数,然后最后对答案的贡献就等于\(\frac{n}{d}\)。
证明得差不多了, 其他地方再去理解理解。
const int MAX_N = 60, MAX_DOM = 1e7 + 200;
ll n, m, arr[MAX_N], phi[MAX_DOM], prime[MAX_DOM], tot, ans;
unordered_map<ll, ll> ump;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll ans = (num * (num + 1)) >> 1;
for (ll d = 2, gx; d <= num; d = gx + 1)
ans -= (gx - d + 1) * varphi(num / d);
for (ll i = 2; i < MAX_DOM; i++)
prime[++tot] = i, phi[i] = i - 1;
for (ll j = 1; j <= tot && i * prime[j] < MAX_DOM; j++)
vis[i * prime[j]] = true;
phi[i * prime[j]] = phi[i] * prime[j];
phi[i * prime[j]] = phi[i] * phi[prime[j]];
for (int i = 2; i < MAX_DOM; i++)
for (int i = 1; i <= m; i++)
if (factor % gcd(arr[i], n) == 0)
ans += varphi(n / factor) - varphi(n / factor - 1);
freopen("running.in", "r", stdin);
freopen("running.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++)
for (int i = 1; i * i <= n; i++)
// running.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 60, MAX_DOM = 1e7 + 200;
ll n, m, arr[MAX_N], phi[MAX_DOM], prime[MAX_DOM], tot, ans;
unordered_map<ll, ll> ump;
bool vis[MAX_DOM];
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll varphi(ll num)
{
if (num < MAX_DOM)
return phi[num];
if (ump.count(num))
return ump[num];
ll ans = (num * (num + 1)) >> 1;
for (ll d = 2, gx; d <= num; d = gx + 1)
{
gx = num / (num / d);
ans -= (gx - d + 1) * varphi(num / d);
}
return ump[num] = ans;
}
void sieve()
{
phi[1] = 1;
for (ll i = 2; i < MAX_DOM; i++)
{
if (!vis[i])
prime[++tot] = i, phi[i] = i - 1;
for (ll j = 1; j <= tot && i * prime[j] < MAX_DOM; j++)
{
vis[i * prime[j]] = true;
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
for (int i = 2; i < MAX_DOM; i++)
phi[i] += phi[i - 1];
}
void solve(int factor)
{
for (int i = 1; i <= m; i++)
if (factor % gcd(arr[i], n) == 0)
{
ans += varphi(n / factor) - varphi(n / factor - 1);
break;
}
}
int main()
{
freopen("running.in", "r", stdin);
freopen("running.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%d", &arr[i]);
sieve();
for (int i = 1; i * i <= n; i++)
if (n % i == 0)
{
solve(i);
if (n / i != i)
solve(n / i);
}
printf("%lld", n - ans);
return 0;
}
// running.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 60, MAX_DOM = 1e7 + 200;
ll n, m, arr[MAX_N], phi[MAX_DOM], prime[MAX_DOM], tot, ans;
unordered_map<ll, ll> ump;
bool vis[MAX_DOM];
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll varphi(ll num)
{
if (num < MAX_DOM)
return phi[num];
if (ump.count(num))
return ump[num];
ll ans = (num * (num + 1)) >> 1;
for (ll d = 2, gx; d <= num; d = gx + 1)
{
gx = num / (num / d);
ans -= (gx - d + 1) * varphi(num / d);
}
return ump[num] = ans;
}
void sieve()
{
phi[1] = 1;
for (ll i = 2; i < MAX_DOM; i++)
{
if (!vis[i])
prime[++tot] = i, phi[i] = i - 1;
for (ll j = 1; j <= tot && i * prime[j] < MAX_DOM; j++)
{
vis[i * prime[j]] = true;
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
for (int i = 2; i < MAX_DOM; i++)
phi[i] += phi[i - 1];
}
void solve(int factor)
{
for (int i = 1; i <= m; i++)
if (factor % gcd(arr[i], n) == 0)
{
ans += varphi(n / factor) - varphi(n / factor - 1);
break;
}
}
int main()
{
freopen("running.in", "r", stdin);
freopen("running.out", "w", stdout);
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%d", &arr[i]);
sieve();
for (int i = 1; i * i <= n; i++)
if (n % i == 0)
{
solve(i);
if (n / i != i)
solve(n / i);
}
printf("%lld", n - ans);
return 0;
}
C – Tree
这道题真 tm 的烦。
正常来讲,可以想出一个\(O(n^3)\)的背包转移。但是会超时。考虑优化,记录子树大小和上限大小可以优化成\(O(n^2)\),因为点对的个数是\(O(n^2)\)级别的。
int n, limit, head[MAX_N], current, siz[MAX_N], dp[MAX_N][MAX_N], val[MAX_N];
void addpath(int src, int dst)
edges[current].to = dst, edges[current].nxt = head[src];
void dfs(int u, int fa, int dep)
for (int i = head[u]; i != -1; i = edges[i].nxt)
dfs(edges[i].to, u, dep - 1), siz[u] += siz[edges[i].to];
int bound = min(siz[u], dep);
for (int i = 1; i <= bound; i++)
for (int i = head[u]; i != -1; i = edges[i].nxt)
for (int k = bound; k >= 1; k--)
for (int a = 0; a + k <= bound; a++)
dp[u][a + k] = max(dp[u][a + k], dp[u][k] + dp[edges[i].to][a]);
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &limit);
for (int i = 1; i <= n; i++)
for (int i = 1, u, v; i <= n - 1; i++)
scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
for (int i = 1; i <= limit; i++)
ans = max(ans, dp[1][i]);
// tree.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3005;
int n, limit, head[MAX_N], current, siz[MAX_N], dp[MAX_N][MAX_N], val[MAX_N];
struct edge
{
int to, nxt;
} edges[MAX_N << 1];
void addpath(int src, int dst)
{
edges[current].to = dst, edges[current].nxt = head[src];
head[src] = current++;
}
void dfs(int u, int fa, int dep)
{
siz[u] = 1;
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (edges[i].to != fa)
dfs(edges[i].to, u, dep - 1), siz[u] += siz[edges[i].to];
int bound = min(siz[u], dep);
for (int i = 1; i <= bound; i++)
dp[u][i] = val[u];
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (edges[i].to != fa)
for (int k = bound; k >= 1; k--)
for (int a = 0; a + k <= bound; a++)
dp[u][a + k] = max(dp[u][a + k], dp[u][k] + dp[edges[i].to][a]);
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &limit);
for (int i = 1; i <= n; i++)
scanf("%d", &val[i]);
for (int i = 1, u, v; i <= n - 1; i++)
scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
dfs(1, 0, limit);
int ans = 0;
for (int i = 1; i <= limit; i++)
ans = max(ans, dp[1][i]);
printf("%d", ans);
return 0;
}
// tree.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 3005;
int n, limit, head[MAX_N], current, siz[MAX_N], dp[MAX_N][MAX_N], val[MAX_N];
struct edge
{
int to, nxt;
} edges[MAX_N << 1];
void addpath(int src, int dst)
{
edges[current].to = dst, edges[current].nxt = head[src];
head[src] = current++;
}
void dfs(int u, int fa, int dep)
{
siz[u] = 1;
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (edges[i].to != fa)
dfs(edges[i].to, u, dep - 1), siz[u] += siz[edges[i].to];
int bound = min(siz[u], dep);
for (int i = 1; i <= bound; i++)
dp[u][i] = val[u];
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (edges[i].to != fa)
for (int k = bound; k >= 1; k--)
for (int a = 0; a + k <= bound; a++)
dp[u][a + k] = max(dp[u][a + k], dp[u][k] + dp[edges[i].to][a]);
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &limit);
for (int i = 1; i <= n; i++)
scanf("%d", &val[i]);
for (int i = 1, u, v; i <= n - 1; i++)
scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
dfs(1, 0, limit);
int ans = 0;
for (int i = 1; i <= limit; i++)
ans = max(ans, dp[1][i]);
printf("%d", ans);
return 0;
}