012组合数学——算法备赛
顺序五元组
给定一个整数数组A(长度N大于等于5),请问有多少个五元组(a,b,c,d,e)满足以下条件
- 0<a<b<c<d<e<N
- Aa+Ab=Ac=Ad+Ae
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll sol(unordered_map<int, int>& m, int x) {ll res = 0;for (auto& [v, c] : m) {if (v * 2 == x)res += (c * (c - 1)); //每个v都多算一次,所以最后除以2else if (m.count(x-v))res += c * m[x - v]; //当遍历到x-v时又计算一次,所以最后除以2}return (res >> 1ll);
}
int main() {int N;cin >> N;vector<int> A(N);unordered_map<int, int> m1, m2;for (int i = 0; i < N; i++) {cin >> A[i];m2[A[i]]++; //统计后部分即[c+1,N-1]部分元素频数}ll ans = 0;for (int i = 0; i < N - 2; i++) {m2[A[i]]--;if (i > 1) ans += sol(m1, A[i]) * sol(m2, A[i]);m1[A[i]]++;}cout << ans << endl;return 0;
}
监控竞赛
问题描述
大赛共有N位参赛者,第i位参赛者编号为i,来自编号为Ai的国家。比赛机房的电脑从左到右依次编号1~N,每位参赛者在与自己编号相同的电脑上进行比赛。官方决定对部分参赛者的电脑进行监控。监控方式满足以下条件:
- 监控的电脑数量不为0
- 同一国家的参数者最多只能有两台电脑被监控
- 如果同一国家的两台电脑被监控,它们的距离不能超过d。这里的距离表示两台电脑的编号差值的绝对值。
请求出所有满足条件的监控方案数,结果对10^9+7取余。
原题链接
思路分析
每个国家选取队员相互独立,可以使用乘法原理,计算出每个国家选取队员的方案数,最后总方案数为所有国家选取队员的方案数的乘积,最后去除全0的特殊方案数就是答案。
对于计算每个国家选取队员的方案数,选0或1个的方案数就是1+n
(n为队员数量),选两个时要考虑队员编号距离,可以用二分法快速求解。
代码
#include<bits/stdc++.h>
using namespace std;
const int M = 1e9+7;int main() {int n,d,x;cin>>n>>d;unordered_map<int, vector<int>> mp;for (int i=1; i<=n; i++)cin>>x, mp[x].push_back(i);long long ans = 1;for (auto &[k, vec]: mp) {long long tmp = 1+vec.size(); //初始取0个或1个的方案数for (int i=0; i<vec.size(); i++) //固定选取vec[i]为较小值,求较大值的个数tmp += (upper_bound(vec.begin(), vec.end(), vec[i]+d) - vec.begin()) - i - 1; //减1去除选自己的情况ans *= tmp%M; ans %= M;}cout<<ans-1; //去除全0的特殊方案return 0;
}
公司命名
问题描述
给你一个字符串数组 ideas
表示在公司命名过程中使用的名字列表。公司命名流程如下:
- 从
ideas
中选择 2 个 不同 名字,称为ideaA
和ideaB
。 - 交换
ideaA
和ideaB
的首字母。 - 如果得到的两个新名字 都 不在
ideas
中,那么ideaA ideaB
(串联ideaA
和ideaB
,中间用一个空格分隔)是一个有效的公司名字。 - 否则,不是一个有效的名字。
返回 不同 且有效的公司名字的数目。
原题链接
思路分析
首先建立一个大小为26的哈希集合用于按首字母分类存放后缀(除首字母剩下字符组成的字符串)
然后用两个指针a,b从左往右遍历哈希数组(分别指向一个字母,代表两个参与公司命名的两个集合)
在枚举a集合中的元素ai,看b集合是否存在,若b集合存在遍历到的该元素bi,表明ai与b中的任意元素交换首字母后存在于原数组ideas中,bi与a中任意元素交换首字母后存在于原数组ideas中,所以a,b集合中的这两个对应字符串不参与乘法原理组合
示例:
用m统计不参与乘法原理组合的字符串对数(两集合交集后缀对数),剩下的元素按乘法原理组合所得的结果就是从当前两个集合选取两个字符串组成合法有效公司名字的组合数
接续选取两个集合,统计一共有ans
个合法有效的字符串组合数,2*ans(乘2是因为两字符串可以调换顺序)就是总的不同 且有效的公司名字的数目。
long long distinctNames(vector<string>& ideas) {unordered_set<string> groups[26];for (auto& s : ideas) {groups[s[0] - 'a'].insert(s.substr(1)); // 按照首字母分组}long long ans = 0;for (int a = 1; a < 26; a++) { // 枚举所有组对for (int b = 0; b < a; b++) {int m = 0; // 交集的大小for (auto& s : groups[a]) {m += groups[b].count(s);}ans += (long long) (groups[a].size() - m) * (groups[b].size() - m);}}return ans * 2; // 乘 2 放到最后}
时间复杂度:O(nm∣Σ∣),其中 n 为 ideas 的长度,m≤10 为单个字符串的长度,∣Σ∣=26 是字符集合的大小。
总结
本题关键是将普通的数组转换为按前缀分组的哈希集合数组,再在哈希集合数组上遍历,比暴力法直接在原数组上做两重循环(复杂度O(n^2*m)要优。某些场景下预处理原始数据对解决问题很有必要。
只含3种数字的n位数
问题描述
给定一个整数n,求恰好包含3种数字(0~9中的3种数字)的n位数有多少个?该n位数不含前导0,结果对1e9+7
取余
输入:第一行输入t(1<=t<=1e5),表示有t个测试用例
接下来t行,每行输入一个n(1<=n<=1e5),表示数值位数
输出:对于每个测试用例,输出一个整数代表答案
代码
#include <iostream>
using namespace std;
/*
dp[i][j] i 位数 j+1 种不同的数字
*/long long dp[100001][3];
int mid = 1e9 + 7;
int main()
{int t;cin >> t;dp[3][2] = 9*9*8; dp[3][1] = 9 * 9 * 2 + 9 * 9;//排列组合for (int i = 4; i <= 100000; i++){dp[i][1] = (dp[i - 1][1] * 2 + 9 * 9) % mid;dp[i][2] = (dp[i - 1][1] * 8 + dp[i - 1][2] * 3) % mid;}while (t--) {int x;cin >> x;if (x < 3) cout << 0 << endl;else cout << dp[x][2] << endl;}return 0;
}
必胜区间
N位师傅依次落座,第i位师傅的资历值为Ai。从左往右,师傅的资历值逐级递增。同时,每位师傅带来了自己精心制作的产品,其第i位师傅的产品的评分依次为Bi。
选择一个区间[L,R],让这个区间的师傅们两两比拼:
- 如果
师傅i
的产品评分小于师傅j
的产品评分(i<j),则双方交换产品,否则不交换。 - 双方的得分为
资历值+持有产品评分
,得分高者获胜,得分相同平局。
必胜区间定义:区间长度大于等于2,区间内任意两个师傅比拼,资历高的师傅都必定获胜。
请问必胜区间有多少个?
原题链接
思路分析
师傅按资历值Ai排列,若A1+max(B1,B2)<A2+min(B1,B2),A2+max(B2,B3)<A3+min(B2,B3)
那么A1+max(B1,B2)<A3+min(B2,B3)
证明如下:
若A1+max(B1,B2)<A2+min(B1,B2)
得max(B1,B2)-min(B1,B2)<A2-A1
,那么B2-B1<A2-A1
恒成立。
同理若A2+max(B2,B3)<A3+min(B2,B3)
,那么B3-B2<A3-A2
恒成立
两式合并得(B3-B2)+(B2-B1)<(A3-A2)+(A2-A1)
化简得B3-B1<A3-A1
,
- 当B1=min(B1,B3),根据
B3-B1<A3-A1
,A1+B3<A3+B1
即A1+max(B1,B2)<A3+min(B2,B3)
。 - 当B3=min(B1,B3),即B3<=B1,因为A1<A3,那么
A1+B3<A3+B1
即A1+max(B1,B2)<A3+min(B2,B3)
。
综上所述:如果A1+max(B1,B2)<A2+min(B1,B2),A2+max(B2,B3)<A3+min(B2,B3)
那么必有A1+max(B1,B2)<A3+min(B2,B3)
。
根据以上结论,延伸一下,不难得出,对于i<j<k,如果Ai+max(Bi,Bj)<Aj+min(Bi,Bj),Aj+max(Bj,Bk)<Ak+min(Bj,Bk)
那么必有Ai+max(Bi,Bk)<Ak+min(Bj,Bk)
。说明师傅的总得分符合递推式。
如果一个区间内所有相邻的师傅都满足必胜条件,那么区间内任意连个师傅也必然满足必胜区间。两者是充分必要的关系。
那么解决此问题只需O(n)的时间复杂度,因为判断相邻师傅就行。剩下一步就是数区间了,先找到尽可能大的区间,那么它的所有长度大于等于2的子区间都必然满足条件,对于长度为x的区间,它的所有长度大于等于2的子区间个数为1+2+…+x-1,即(x*(x-1))/2
。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// #define int long long
int n, k1 = 1;
long long sum=0;
vector<int> v1, v2;
signed main()
{cin >> n;for (int i = 0, i1; i < n; i++, v1.push_back(i1))cin >> i1;for (int i = 0, i1; i < n; i++, v2.push_back(i1))cin >> i1;for (int i = 1; i < n; i++){if (v1[i] + min(v2[i - 1], v2[i]) > v1[i - 1] + max(v2[i - 1], v2[i]))k1++;elsesum += (long long)k1 * (k1 - 1) / 2, k1 = 1;}if (k1 != 1)sum += (long long)k1 * (k1 - 1) / 2;cout << sum;
}