前言
板刷题单:https://www.luogu.com.cn/training/2018#problems
AT5741 Fusing Slimes
可以想到,每种情况都对应一个长度为 n - 1 的排列。
考虑计算每条路径被经过了多少次, 先对原数组做差分。
记 d(i) 为 i to i 1 的长度,f(i) 为 i to i 1 的路径被经过的期望次数。
E = sum limits _{i = 1}^{n-1}d(i) times f(i)
考虑如何计算 f(i)。
定义 g(j,i) 为点 j 向右融合经过 i to i 1 这条路径的期望次数。
有:f(i) = sum limits _{j = 1}^i g(j,i)
问题在于计算 g(j,i),考虑 j 点能顺畅地右移到 i 1,那么 [j 1,i] 间的点都已经右移。
表现在序列上,就是 j 的出现位置一定在 [j 1,i] 之后。
可以考虑先在移动序列中对 [j,i] 间的 i - j 1 个元素去序,然后将 j 放在最后,剩余 i - j 个元素进行全排列,放回原序列中。
这样就较为简单地推出了 g(j,i),而不需要别的题解中我看不懂的奇怪式子。(
还可以进一步化简:
而答案就是:
可以线性递推逆元,预处理调和级数前缀和做到 O(n) 复杂度。
代码语言:javascript复制const int maxn = 1e5 100;
const int mod = 1e9 7;
inline int add(int x, int y) { int res = x y; return res >= mod ? res - mod : res; }
inline int mul(int x, int y) { return 1ll * x * y % mod; }
int n, a[maxn], inv[maxn], sum[maxn], res, prod = 1;
int main()
{
read(n);
for (int i = 1; i <= n; i) read(a[i]);
inv[1] = sum[1] = 1;
for (int i = 2; i <= n; i) sum[i] = add(sum[i - 1], inv[i] = mul(inv[mod % i], mod - mod / i));
for (int i = 1; i < n; i) prod = mul(prod, i), res = add(res, mul(a[i 1] - a[i], sum[i]));
printf("%dn", mul(prod, res));
return 0;
}
AT4815 [ABC154E] Almost Everywhere Zero
K le 3,怎么浓浓的一股分类讨论气味……
虽然一眼觉得是数位 DP……
然而我觉得数位 DP 是可以的,于是打之。
确实是可以的。那这题有点水。
代码语言:javascript复制#include <cstdio>
#include <cstring>
const int maxn = 110;
char s[maxn];
int num[maxn], n, k;
long long f[maxn][4][2][2];
long long dfs(int pos, int limit, int zero, int sum)
{
if (sum > k) return 0;
if (f[pos][sum][limit][zero] != -1) return f[pos][sum][limit][zero];
if (pos == n 1) return sum == k;
int up = limit ? num[pos] : 9;
long long res = 0;
for (int i = 0; i <= up; i) res = dfs(pos 1, i == up && limit, zero && !i, sum (i > 0));
return f[pos][sum][limit][zero] = res;
}
int main()
{
scanf("%sn%d", s 1, &k);
memset(f, -1, sizeof(f));
n = std::strlen(s 1);
for (int i = 1; i <= n; i) num[i] = s[i] - '0';
printf("%lldn", dfs(0, 1, 1, 0));
return 0;
}
AT5295 [ABC154F] Many Many Paths
单个 f(i,j) 可以直接通过组合数计算。考虑在 i j 步操作中选出 i 步向下即可。
然后考虑如何求解下面这个式子:
只有上半部分变化,看起来是有一定性质的。
考虑先把 j 改成从零开始,这样比较有意义,所以要做一个前缀和一样的减法。
问题转化为,计算:
这个其实就是 Hockey-Stick Identity 反过来。
证明的话,可以考虑将待选择序列划分为 [1,i] 与 [i 1,i k 1],然后枚举从右往左第一个选择的物品。
这样就可以大幅简化计算了。
还可以把组合数拆开。
接下来大家都知道怎么做了吧。
代码语言:javascript复制const int mod = 1e9 7;
const int maxn = 2e6 100;
int r1, r2, c1, c2, prod[maxn], prodinv[maxn], inv[maxn], res;
inline int add(int x, int y) { int res = x y; return res >= mod ? res - mod : res; }
inline int mul(int x, int y) { return 1ll * x * y % mod; }
inline int c(int n, int m) { return mul(prod[n], mul(prodinv[n - m], prodinv[m])); }
int main()
{
read(r1), read(c1), read(r2), read(c2);
inv[1] = 1;
int up = r2 c2 10;
prod[0] = prodinv[0] = prod[1] = prodinv[1] = 1;
for (int i = 2; i <= up; i)
prod[i] = mul(prod[i - 1], i), prodinv[i] = mul(prodinv[i - 1], inv[i] = mul(inv[mod % i], mod - mod / i));
for (int i = r1; i <= r2; i)
res = add(res, add(c(i c2 1, i 1), mod - c(i c1, i 1)));
printf("%dn", res);
return 0;
}
AT4867 [ABC155D] Pairs
神秘题。 没啥想法,以为是结论题。看了题解才知道是二分。 知道是二分就好做了。
讨论一下,然后根据单调性双指针, 分为 负负,正负,正正分别讨论。
代码语言:javascript复制#include <algorithm>
#include <cstdio>
#include <ctype.h>
const int bufSize = 1e6;
#define int long long
inline char nc()
{
#ifdef DEBUG
return getchar();
#endif
static char buf[bufSize], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1 ;
}
template <typename T>
inline T read(T& r)
{
static char c;
static int flag;
flag = 1, r = 0;
for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1;
for (; isdigit(c); c = nc()) r = r * 10 c - 48;
return r *= flag;
}
const int maxn = 2e5 100;
int n, k, a[maxn], zeromid;
int check(long long mid)
{
int p = zeromid, res = 0;
for (int i = 1; i < zeromid; i)
{
while (1ll * a[i] * a[p] > mid && p <= n) p;
if (p <= n) res = (n - p 1);
}
p = n;
for (int i = zeromid; i <= n; i)
{
while (1ll * a[i] * a[p] > mid && p > 0) --p;
if (p > i) res = p - i;
}
p = 1;
for (int i = zeromid - 1; i > 0; --i)
{
while (1ll * a[i] * a[p] > mid && p < zeromid) p;
if (p < i) res = i - p;
}
return res;
}
template <typename T>
inline T abs(const T& x)
{
return x > 0 ? x : -x;
}
signed main()
{
read(n), read(k);
for (int i = 1; i <= n; i) read(a[i]);
std::sort(a 1, a 1 n);
int maxx = std::max(abs(a[n]), abs(a[1]));
long long l = -(maxx * maxx), r = maxx * maxx, mid, ans = 0;
for (int i = 1; !zeromid && i <= n; i) if (a[i] >= 0) zeromid = i;
while (l <= r)
{
mid = (l r) >> 1;
if (check(mid) >= k) ans = mid, r = mid - 1;
else l = mid 1;
}
printf("%lldn", ans);
return 0;
}
AT4866 [ABC155E] Payment
看上去就是动态规划之类的解法吧。 题意转化为 x ge 0,最小化 f(x N) f(x),然后考虑。 对于 f(x N),从后往前,每位对下一位的影响就是是否进位,而且进位也只进一位。
而 x 的这位填多少?考虑填完之后,要么 x N 这一位为零,要么 x 这一位为零。 塞到状态里就可以了。
g(i,0) 代表第 i 位未进位, g(i,1) 代表第 i 位向 i 1 进位了,然后转移随便搞搞就行了。
代码语言:javascript复制#include <cstdio>
#include <algorithm>
#include <cstring>
#include <ctype.h>
const int bufSize = 1e6;
using std::min;
inline char nc()
{
#ifdef DEBUG
return getchar();
#endif
static char buf[bufSize], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1 ;
}
inline void read(char *s)
{
static char c;
for (; !isdigit(c); c = nc());
for (; isdigit(c); c = nc()) *s = c;
*s = '