整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
红书《题目与解读》第一章 数学 题解《ACM国际大学生程序设计竞赛题目与解读》
目录
第一章 数学
1.1 概率
Problem A.Coupons (几何概型,概率)
UVA 10288
Problem
一共有 n n n 种不同的优惠券,每次得到一个优惠券的概率相同,问期望多少次得到所有 n n n 种优惠券,以带分数的形式输出。
Solution
方法一:
设 f [ i ] f[i] f[i] 表示已经买到 i i i 个优惠券的期望购买次数。
考虑最后一次购买,若买到的是一个新优惠券,则:
f [ i ] + = ( f [ i − 1 ] + 1 ) × n − ( i − 1 ) n f[i] += (f[i-1]+1)\times \cfrac{n-(i-1)}{n} f[i]+=(f[i−1]+1)×nn−(i−1)
若买到的是一个已经买过但不是第 i i i 个买的优惠券,则:
f [ i ] + = ( f [ i ] + 1 ) × i − 1 n f[i]+=(f[i]+1)\times \frac{i-1}{n} f[i]+=(f[i]+1)×ni−1
整理得:
f [ i ] = f [ i − 1 ] + n n − i + 1 f[i]=f[i-1]+\frac{n}{n-i+1} f[i]=f[i−1]+n−i+1n
即:
a n s = ∑ i = 1 n n n − i + 1 = ∑ i = 1 n n i ans = \sum_{i = 1}^{n}\cfrac{n}{n-i+1}=\sum_{i=1}^{n}\cfrac{n}{i} ans=i=1∑nn−i+1n=i=1∑nin
显然最后的答案就是调和级数前缀和。
若数据较大的话可以 O ( 1 ) O(1) O(1) 计算调和级数前缀和:
调和级数 ∑ i = 1 ∞ 1 n \displaystyle\sum_{i = 1}^{∞}\cfrac{1}{n} i=1∑∞n1 的极限为 ln n + C \ln n+C lnn+C,其中 C = 0.57721566490153286060651209 C=0.57721566490153286060651209 C=0.57721566490153286060651209 是欧拉常数
方法二:
红书上的题解
当前已有 k k k 种,显然得到新优惠券的概率为 n − k n \cfrac {n-k} n nn−k,显然是几何概型,所以期望是 n n − k \cfrac {n}{n-k} n−kn,所以答案就是 n n + n n − 1 + ⋯ + n 1 = n × ∑ i = 1 n 1 i \displaystyle \cfrac n n+ \cfrac {n}{n-1}+\cdots+\cfrac{n}{1}=n\times \sum\limits_{i=1}^{n}\cfrac 1 i nn+n−1n+⋯+1n=n×i=1∑ni1
Hint
数据较大,注意约分,除掉 gcd \gcd gcd
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
//#define ll __int128;
typedef long long ll;
const int N = 107;
int n, m;
int up[N], down[N];
ll lcm(int a, int b)
{
return a / __gcd(a, b) * b;
}
int get_len(int x)
{
int len = 0;
while(x) {
x /= 10;
len ++ ;
}
return len;
}
void solve()
{
ll LCM = 1;
for(int i = 1; i <= n; ++ i) {
up[i] = n;
down[i] = i;
LCM = lcm(LCM, i);
}
ll sum = 0;
for(int i = 1; i <= n; ++ i) {
sum += n * (LCM / i);
}
ll d = __gcd(sum, LCM);
sum /= d;
LCM /= d;
if(LCM == 1) {
cout << sum << endl;
return ;
}
ll mo = sum % LCM;
ll l = sum / LCM;
for(int i = 1; i <= get_len(l) + 1; ++ i) cout << " ";
cout << mo << endl;
cout << l << " ";
for(int i = 1; i <= get_len(LCM); ++ i) cout << "-";
puts("");
for(int i = 1; i <= get_len(l) + 1; ++ i) cout << " ";
cout << LCM << endl;
}
signed main()
{
while(scanf("%lld", &n) != EOF) {
solve();
}
return 0;
}
Problem B.Generator (KMP,期望,高斯消元)
ZOJ 2619
Problem
给定一个字符串 S S S 和字符集大小 n n n 。要求另生成一个字符串,它一开始为空,每次平均且独立地随机生成一个字符集中的字符添加到其末尾,生成出字串 S S S 时停下,求所生成字符串的长度的期望。
Solution
显然生成的字符串越来越长,每次由 n n n 种字符选择,那么就有 i n i^n in 种方案数,杂乱无章的无从下手。所以从对答案的贡献角度出发,发现对于答案而言,有用的只有最后生成的字符串 T T T 的后缀与模式串 S S S 的匹配长度。因此很多杂乱的字符串实际上对于答案而言是同一种状态,即一共只有 0 ∼ L 0\sim L 0∼L 种状态,表示两字符串匹配的长度。
这样就有了清晰的状态,考虑状态如何转移即可。
书中倒推由于都是未知的需要使用高斯消元解方程组,比较麻烦,精度还不能得到保障。我们这里利用一个小技巧,直接正推。利用 KMP , O ( n ) O(n) O(n) 求出失配数组 nex i , j \text{nex}_{i,j} nexi,j(当然要在失配的时候用)
反过来设 f[i]
为从状态 0 0 0 到状态 i i i 期望次数,答案显然就是 f[len]
则可以把原转移方程直接改写为:
f [ i ] = f [ i + 1 ] n + 1 n ∑ j = 0 n − 1 f [ nex [ i + ′ A ′ ] ] − 1 f[i] = \frac{f[i+1]}{n}+\frac{1}{n}\sum_{j=0}^{n-1}{f[\text{nex}[i + 'A\ ']]} - 1 f[i]=nf[i+1]+n1j=0∑n−1f[nex[i+′A ′]]−1
就是 f [ i ] f[i] f[i] 由下一步匹配成功的 f [ i + 1 ] f[i+1] f[i+1] 与未匹配成功的 ∑ j = 0 n − 1 f [ nex [ i + ′ A ′ ] ] \displaystyle \sum_{j=0}^{n-1}{f[\text{nex}[i + 'A\ ']]} j=0∑n−1f[nex[i+′A ′]] 减去一次期望操作转移而来。
化简成正推的形式即:
f [ i + 1 ] = ( f [ i ] + 1 ) × n − ∑ j = 0 n − 1 f [ nex [ i + ′ A ′ ] ] f[i+1] = (f[i] + 1)\times n - \sum_{j=0}^{n-1}{f[\text{nex}[i + 'A\ ']]} f[i+1]=(f[i]+1)×n−j=0∑n−1f[nex[i+′A ′]]
初始化 f[0] = 0
,然后 O ( n ) O(n) O(n) 正序递推即可。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 50;
int n, m, k, t, ans, kcase, cases;
int a[N];
int nex[N];
char s[N];
ll f[N];
int len;
void get_nex(char* s)
{
for (int i = 2, j = 0; i <= len; ++ i) {
while(j != 0 && s[j + 1] != s[i])
j = nex[j];
if(s[j + 1] == s[i])
++ j;
nex[i] = j;
}
}
void solve()
{
scanf("%d%s", &n, s + 1);
len = strlen(s + 1);
get_nex(s);
f[0] = 0;
for (int i =0; i <= len - 1; ++ i) {
f[i + 1] = (f[i] + 1) * n;
for (int j = 0; j < n; ++ j) {
if(s[i + 1] == 'A' + j)
continue;
int pos = i;
while(pos && s[pos + 1] != j + 'A')
pos = nex[pos];
if(s[pos + 1] == j + 'A')
++ pos;
f[i + 1] -= f[pos];
}
}
printf("%lld\n", f[len]);
}
int main()
{
scanf("%d", &t);
while(t -- ) {
printf("Case %d:\n", ++ kcase);
solve();
if(t)
puts("");
}
return 0;
}
Problem C.Dinner with Schwarzenegger!!! (概率)
UVA10217
Problem
有若干人排队买电影票,如果某个人的生日与排在他前面的某个人的生日相同,那么他讲中奖。中奖的机会只有一个,给所有中奖者中排在最前面的那一位。排在第一位的人如果与买票者的生日相同,那么他将中奖。如果一年有 n n n 天,求排在什么位置的中奖概率最大,和理论上的最佳实数位置。
Solution
设第 i i i 个人的中奖概率是 f[i]
,显然有:
f [ 1 ] = 1 n f[1] = \cfrac 1 n f[1]=n1
f [ 2 ] = n − 1 n × 1 n f[2] = \cfrac{n-1}{n} \times \cfrac 1 n f[2]=nn−1×n1
. . . ... ...
f [ i ] = n − 1 n × n − 1 n × n − 2 n × . . . × n − i + 2 n × i − 1 n f[i] = \cfrac{n-1}n \times \cfrac{n-1} n \times \cfrac{n-2} n \times ...\times \cfrac {n-i+2} n \times \cfrac{i-1} n f[i]=nn−1×nn−1×nn−2×...×nn−i+2×ni−1
f [ i + 1 ] = n − 1 n × n − 1 n × n − 2 n × . . . × n − i + 1 n × i n f[i+1] = \cfrac {n-1} n \times \cfrac {n-1} n \times \frac {n-2} n \times ...\times \cfrac{n-i+1} n \times \cfrac i n f[i+1]=nn−1×nn−1×nn−2×...×nn−i+1×ni
有
f [ i ] f [ i + 1 ] = ( i − 1 ) × n ( n − i + 1 ) × i \cfrac {f[i]}{f[i+1]} = \cfrac {(i-1)\times n}{(n-i+1)\times i} f[i+1]f[i]=(n−i+1)×i(i−1)×n
显然概率越来越小, f [ i ] f [ i + 1 ] ≥ 1 \cfrac {f[i]}{f[i+1]} \ge 1 f[i+1]f[i]≥1 解得:
1 − 4 × n + 1 2 ≤ i ≤ 1 + 4 × n + 1 2 \cfrac{1-\sqrt{4\times n+1} } {2} \le i \le \cfrac{1+\sqrt{4\times n+1}} {2} 21−4×n+1≤i≤21+4×n+1
最佳整数位置为 ⌈ 1 + 4 × n + 1 2 ⌉ \left \lceil\cfrac {1+\sqrt{4\times n+1}} 2\right\rceil ⌈21+4×n+1⌉,最佳实数位置为 − 1 + 4 × n + 1 2 \cfrac {-1+\sqrt{4\times n+1} }2 2−1+4×n+1。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7, maxm = maxn << 1 | 7;
int n, m, s, t;
int a[maxn];
int main()
{
while(scanf("%d", &n) != EOF) {
double ans = (-1.0 + sqrt(1.0 + 4.0 * n)) / 2.0;
int ans2 = ans + 1;
printf("%.2lf %d\n", ans, ans2);
}
return 0;
}
1.2 代数
1.2.1 Polya
Problem A.Arif in Dhaka (Polya,等价类计数)
UVA 10294
Problem
给你一串珠子(连接成了一个环),共有 n n n 个珠子组成,你有 t t t 种颜色,现在你来给这个珠子染色,问染成项链有多少种方法?染成手镯有多少种方法?在项链里,经过顺时针旋转后相同的算一个,在手镯里,经过顺时针旋转或者沿着对称轴兑换后一样的算一个。
Solution
Code
1.2.2 矩阵
Problem A.Tower
HDU 2971
Problem
a 1 = 1 a_1=1 a1=1,给定 a 2 a_2 a2,设 a n = 2 a 2 × a n − 1 − a n − 2 a_n=2a_2\times a_{n-1}-a_{n-2} an=2a2×an−1−an−2,求 s n = a 1 2 + a 2 2 + ⋯ + a n 2 s_n=a_1^2+a_2^2+\cdots+a_n^2 sn=a