「Summary」2021-11-27 模拟赛
T1「BZOJ3260」跳
Sol
这道题先把 \(C(x,y)\) 打出来,可以发现 ta 就是个组合数(杨辉三角)。
1 1 1 1 1 1 1 1
1 2 3 4 5 6 7 8
1 3 6 10 15 21 28 36
1 4 10 20 35 56 84 120
1 5 15 35 70 126 210 330
1 6 21 56 126 252 462 792
1 7 28 84 210 462 924 1716
1 8 36 120 330 792 1716 3432
不难猜出结论:一定是先沿着第一行或第一列走,在横着或竖着走到 \((n,m)\) 。
所以要先区分 \(n,m\) 的大小,由于 \(n*m \leq 10^{12}\) ,所以 \(n,m\) 中较小的一个一定 \(\leq 10^6\) 。
记 \(x\) 为 \(n,m\) 中较小者,\(y\) 为较大者。
写出式子然后再化简:
\(Ans=x+\sum\limits_{i=1}^y \binom{x+i}{i}\)
\(=x+\sum\limits_{i=1}^y \binom{x+i}{x}\)
\(=x+\binom{x+y+1}{x}\)
但是 \(n,m\) 还是很大,按正常方式求肯定不行。
回到阶乘式:\(\binom{n}{m}=\frac{n!}{m!(n-m)!}=\frac{n(n-1)...(n-m+1)}{m!}\)
可以发现,分子分母都只有 \(m\) 项,而本题中 \(y \leq 10^6\) ,所以直接循环求组合数就行了
Code
#include <cstdio>
#include <cstring>
#define Maxn 1000
#define LL long long
#define Mod 1000000007
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)
LL N, M;
template < typename _T >
_T Min (_T x, _T y) { return x < y ? x : y; }
template < typename _T >
_T Max (_T x, _T y) { return x > y ? x : y; }
LL Pow (LL x, LL y) {
LL ret = 1;
for (LL i = y; i; i >>= 1) {
if (i & 1) ret = ret * x % Mod;
x = x * x % Mod;
}
return ret;
}
LL C (LL x, LL y) {
LL ret = 1;
for (LL i = x - y + 1; i <= x; i ++) ret = ret * (i % Mod) % Mod;
for (LL i = 1; i <= y; i ++) ret = ret * Pow (i, Mod - 2) % Mod;
return ret;
}
int main () {
scanf ("%lld %lld", &N, &M);
LL n = Max (N, M);
LL m = Min (N, M);
printf ("%lld", (C (n + m + 1, m) + (n % Mod)) % Mod);
return 0;
}
T2「HAOI2010」计数
Sol
刚开始看到这个题 \(n \leq 10^{50}\) ,很自然的会想到数位DP,是可做的,但按我的思路状态不能很好的表示,所以无了。
问题实际上可以转化为给你 \(len\) 个可重复数字,求 \(n\) 在这些数字全排列中的次序。(有前导零的数如 \(0012\) 当作 \(12\))
若数字不重复,就是经典的康托展开问题了。这里有重复的,思路是几乎一样的。
康托展开的结果即是:
记 \(n=\overline{n_1 n_2 n_3 ... n_k}\) ,\(x_i=\sum\limits_{j=1}^{k} [n_j<n_i]\)
\(Ans'=\sum\limits_{i=1}^k x_i·(n-i)!\)
这里存在重复所以需将剩下 \((n-i)\) 位上的排列改为可重排列。
假设剩下 \((n-i)\) 上要填数字 \(i\) 的个数为 \(s_i\) ,\(T=\sum s_i\)
填 \(0\) 的方案为 \(\binom{T}{s_0}\)
填 \(1\) 的方案为 \(\binom{T-s_0}{s_1}\)
填 \(2\) 的方案为 \(\binom{T-s_0-s_1}{s_2}\)
\(...\)
故总方案为 \(\binom{T}{s_0} \binom{T-s_0}{s_1} ... \binom{T-s_0-...-s_7}{s_8}\)
所以答案为 \(Ans=\sum\limits_{i=1}^{len} n_i \binom{T}{s_{i,0}} \binom{T-s_{i,0}}{s_{i,1}} ... \binom{T-s_{i,0}-...-s_{i,7}}{s_{i,8}}\)
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define Maxn 100
#define LL long long
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)
int len;
int cnt[10];
char s[Maxn + 5];
LL C[Maxn + 5][Maxn + 5];
void Init (int N) {
rep (i, 0, N) {
C[i][0] = 1;
rep (j, 1, N) {
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
}
LL Value (int n) {
LL tot = 1;
rep (i, 0, 9) {
if (cnt[i]) {
tot *= C[n][cnt[i]];
n -= cnt[i];
}
}
return tot;
}
int main () {
scanf ("%s", s + 1);
len = strlen (s + 1);
Init (len);
rep (i, 1, len) cnt[s[i] - '0'] ++;
LL ans = 0;
rep (i, 1, len) {
rep (j, 0, s[i] - '0' - 1) {
if (cnt[j]) {
cnt[j] --;
ans += Value (len - i);
cnt[j] ++;
}
}
cnt[s[i] - '0'] --;
}
printf ("%lld", ans);
return 0;
}
T3 「HDU 某某题」编码
Sol
这道题挺套路,容斥的基本题吧。
问题可以转化为:
\(\sum\limits_{i=1}^m a_i = k\) 其中 \(a_i \in [1,n-1]\) 。
考虑容斥 \(\geq n\) 的部分
枚举多少个数超过 \(n-1\) 。再将剩下的用隔板法计算贡献。
Code
#include <cstdio>
#include <cstring>
#define Maxn 200000
#define LL long long
#define Mod 998244353
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)
int T;
int n, m, k;
LL inv[Maxn + 5], jc[Maxn + 5];
void Init () {
jc[0] = 1;
rep (i, 1, Maxn) jc[i] = jc[i - 1] * i % Mod;
inv[1] = 1;
rep (i, 2, Maxn) inv[i] = (Mod - Mod / i) * inv[Mod % i] % Mod;
inv[0] = 1;
rep (i, 1, Maxn) inv[i] = inv[i - 1] * inv[i] % Mod;
}
LL C (int X, int y) {
if (X < y) return 0;
return jc[X] * inv[y] % Mod * inv[X - y] % Mod;
}
int Min (int x, int y) {
return x < y ? x : y;
}
int main () {
scanf ("%d", &T);
Init ();
while (T --) {
scanf ("%d %d %d", &n, &m, &k);
LL ans = 0;
rep (i, 0, k / n) {
LL res = C (m, i) * C (k - i * n + m - 1, m - 1) % Mod;
if (i & 1) ans = (ans - res + Mod) % Mod;
else ans = (ans + res) % Mod;
}
printf ("%lld\n", ans);
}
return 0;
}
T4 「不清楚来源题」敌对
又是一道容斥题,思路不难想,但考场上记错时间了。。。
每个人有 \(5\) 个爱好,可以分别求出有 \(i\) 有个爱好相同的方案数 \(s_i\) ,容斥系数为 \((-1)^{i+1}\)
可以用 map 或者 hash 处理有多少人有 \(i\) 个爱好相同,最后容斥一下即可。(按 std 的方法写了个 hash 表 map + tire树)
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define Maxn 50000
#define Size 1000007
#define LL long long
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)
int n, tot;
LL ret[10];
int now[10], a[Maxn + 5][10];
struct Node {
int pre, vl, id;
Node () {
}
Node (int x, int y, int z) {
pre = x, vl = y, id = z;
}
} ;
struct Hash_Table {
vector < Node > h[Size + 5];
int Find (int pre, int vl) {
int hsh = ((LL) pre * 13331 + vl) % Size;
rep (i, 0, (int) h[hsh].size () - 1) {
if (h[hsh][i].pre == pre && h[hsh][i].vl == vl) return h[hsh][i].id;
}
return -1;
}
int Insert (int pre, int vl) {
int hsh = ((LL) pre * 13331 + vl) % Size;
h[hsh].push_back (Node (pre, vl, ++ tot));
return tot;
}
} tab;
struct Tire {
int ed[Maxn * 32 + 5];
int Get (int len) {
int P = 0;
rep (i, 1, len) {
int val = now[i];
int u = tab.Find (P, val);
if (u == -1) u = tab.Insert (P, val);
P = u;
}
return ed[P] ++;
}
} tire;
int main () {
scanf ("%d", &n);
rep (i, 1, n) {
rep (j, 1, 5) {
scanf ("%d", &a[i][j]);
}
sort (a[i] + 1, a[i] + 1 + 5);
}
rep (p, 1, n) {
rep (i, 1, (1 << 5) - 1) {
int cnt = 0;
rep (j, 1, 5) {
if (i & (1 << (j - 1))) {
now[++ cnt] = a[p][j];
}
}
ret[cnt] += tire.Get (cnt);
}
}
LL ans = 0;
rep (i, 1, 5) {
if (i & 1) ans += ret[i];
else ans -= ret[i];
}
printf ("%lld", 1ll * n * (n - 1) / 2 - ans);
return 0;
}