C - Tsundoku
不能贪心
例如第一组是8 1 1 1 1 1 1…
第二组是7 7 7 7 7 7 7…
如果贪心去选,始终会选第二组。而正确答案显然是选第一组
正确的方法是二分答案,并在答案n中遍历第一组选取的i与第二组选取的n-i个数
**# -*- coding: utf-8 -*-
# @time : 2023/6/2 13:30
# @file : atcoder.py
# @software : PyCharm
import bisect
import copy
import sys
from itertools import permutations
from sortedcontainers import SortedList
from collections import defaultdict, Counter, deque
from functools import lru_cache, cmp_to_key
import heapq
import math
sys.setrecursionlimit(1000)
def main():
items = sys.version.split()
if items[0] == '3.10.6':
fp = open("in.txt")
else:
fp = sys.stdin
n, m, k = map(int, fp.readline().split())
a = list(map(int, fp.readline().split()))
b = list(map(int, fp.readline().split()))
sa = [0] * (n + 1)
sb = [0] * (m + 1)
for i in range(n):
sa[i + 1] = a[i] + sa[i]
for i in range(m):
sb[i + 1] = b[i] + sb[i]
def check(l):
for i in range(0, l + 1):
if 0 <= i <= n and 0 <= l - i <= m and sa[i] + sb[l - i] <= k:
return True
return False
lo, hi = 0, n + m + 1
while lo < hi:
mi = lo + hi >> 1
if check(mi):
lo = mi + 1
else:
hi = mi
print(lo - 1)
if __name__ == "__main__":
main()
**
D - Sum of Divisors
简单数论题
虽然N很大,但是可以在
O
(
n
∗
l
o
g
(
n
)
)
O(n *log(n))
O(n∗log(n))时间内暴力解决,即调和函数
这里采用了另一种解法,在
O
(
k
n
)
时间内暴力解决
O(kn)时间内暴力解决
O(kn)时间内暴力解决,k大约在30左右
首先可以得到
F
(
n
)
F(n)
F(n)是积性函数,即
F
(
a
b
)
=
F
(
a
)
F
(
b
)
F(ab)=F(a)F(b)
F(ab)=F(a)F(b)如果
a
b
ab
ab互质
这样就可以通过质因数分解的方式,枚举出能整除n的质数a,然后通过上述公式计算
F
(
n
)
F(n)
F(n)
// atcoder.cpp :
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;
typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;
int n;
vi p;
LL f[10000001];
int main()
{
//freopen("in.txt", "r", stdin);
for (int i = 2; i < 10000; ++i) {
bool flag = 1;
for (int j = 0; j < p.size(); ++j)
{
if (i % p[j] == 0) {
flag = 0;
break;
}
if (p[j] * p[j] >= i)
break;
}
if (flag)
p.push_back(i);
}
LL ans = 0;
scanf("%d", &n);
f[1] = 1;
ans = 1;
for (int i = 2; i <= n; ++i) {
int t = i;
for (int j : p) {
if (t % j == 0) {
int c = 0;
while (t % j == 0) {
t /= j;
c++;
}
f[i] = f[t] * (c + 1);
break;
}
if (j * j >= i)
break;
}
if (t == i) f[i] = 2;
ans += i * f[i];
}
printf("%lld\n", ans);
return 0;
}
E - NEQ
一道不错的组合数学题目
解法1:重排问题
我们先考虑标准的重排问题:对一个排列 1,2,3,4…n,求重排后没有数字相同的排列数
设f(n)为该问题解
考虑数字n,如图中,6有n-1处可以放置
如果6放置在4处,那么考虑4,
4有两种安排方法
第一种是和6交换位置,原问题变成了f(n-2)
第二种是不放在6的位置,那么原问题变成了排列一个1 2 3 5 4,长度为5的子问题,答案是f(n-1)
所以
f
(
n
)
=
(
n
−
1
)
(
f
(
n
−
1
)
+
f
(
n
−
2
)
)
f(n)=(n-1)(f(n-1)+f(n-2))
f(n)=(n−1)(f(n−1)+f(n−2))
然后我们考虑修改过的重排问题,即本题要求
对一个排列A 1,2,3…n
B排列可以选取 [1,m],要求每个位置不能与A中数字相同
每次有两种选法
第一种选N,因此可以转换为重排问题的做法
f
(
n
)
=
(
n
−
1
)
(
f
(
n
−
1
)
+
f
(
n
−
2
)
)
f(n)=(n-1)(f(n-1)+f(n-2))
f(n)=(n−1)(f(n−1)+f(n−2))
第二种选N数字范围外的
如上图,将原A数组的6用7代替,然后问题就转换为f(n-1)
注意,这里的
f
(
n
)
f(n)
f(n)函数代表意义为排列集合{1…n},并且留有t个多余的比n大的数的情况下所能组成的排列数,使用上图的构造方法,这里的t永远都是M-N
为什么7不能放在位置1.2.3.4.5?因为如果这样进行交换后,新交换进来的数可能比集合里的数要大,不能满足
f
(
n
)
f(n)
f(n)函数的定义。
// atcoder.cpp :
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;
typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;
int n, m;
LL f[500005];
int mod = int(1e9) + 7;
int main()
{
//freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
f[0] = 1;
f[1] = m - n;
for (int i = 2; i <= n; ++i) {
f[i] = ((m - n) * f[i - 1] % mod + (i - 1) * ((f[i - 1] + f[i - 2]) % mod) % mod) % mod;
}
LL ans = f[n];
for (int i = m, j = 0; j < n; i--, j++) {
ans = ans * i % mod;
}
printf("%lld\n", ans);
return 0;
}
解法2:容斥原理
第一种解法其实比较难想,第二种解法更为容易一点
首先确定A数组的排列,和解法1是一致的。然后看B数组。
当难以求“所有位置都不重复”时,我们可以求确定重复一个位置(可能重复更多位置),确定重复两个位置,重复三个。。。一直到重复N个位置的解法。
确定i个位置重复的方案数为
C
(
n
,
i
)
∗
A
(
m
−
i
,
n
−
i
)
C(n,i)*A(m-i,n-i)
C(n,i)∗A(m−i,n−i)
例如 1 2 3 4与 1 3 2 4是确定重复位置1,而1 2 3 4与 1 2 3 4也是确定重复位置1
因为解法集合是偏序包含的,因此可以采用容斥原理
f
(
n
)
=
∑
i
=
0
n
A
(
m
−
i
,
n
−
i
)
C
(
n
,
i
)
(
−
1
)
i
f(n)=\sum_{i=0}^{n} A(m-i,n-i)C(n,i)(-1)^i
f(n)=∑i=0nA(m−i,n−i)C(n,i)(−1)i
// atcoder.cpp :
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;
typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;
int n, m;
int mod = int(1e9) + 7;
LL fac[500005], inv[500005];
LL pw(LL a, LL x) {
if (x == 0) return 1;
LL temp = pw(a, x >> 1);
if (x & 1) {
return temp * temp % mod * a % mod;
}
else {
return temp * temp % mod;
}
}
LL cmb(LL x, LL y) {
return fac[x] * inv[y] % mod * inv[x - y] % mod;
}
LL perm(LL x, LL y) {
return fac[x] * inv[x - y] % mod;
}
int main()
{
//freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
fac[0] = 1;
for (int i = 1; i <= m; ++i)
fac[i] = fac[i - 1] * i % mod;
inv[m] = pw(fac[m], mod - 2LL);
for (int i = m - 1; i >= 0; --i)
inv[i] = inv[i + 1] * (i + 1LL) % mod;
LL ans = 0;
for (int i = 0; i <= n; ++i) {
if (i % 2 == 0)
ans += perm(m - i, n - i) * cmb(n, i) % mod;
else
ans -= perm(m - i, n - i) * cmb(n, i) % mod;
ans %= mod;
if (ans < 0) ans += mod;
}
ans = ans * perm(m, n) % mod;
printf("%lld\n", ans);
return 0;
}