2021年ACM竞赛班训练(三)
2021年ACM竞赛班训练(三)
A题 验证哥德巴赫猜想
请移步验证哥德巴赫猜想
B题 路径
请移步路径
C题 组合数
思路
- 预处理组合数:
\(m\)和\(n\)的范围均在\(2000\)以内, 可以采用\(O(N^2)\)的算法预处理所有的组合数.
求解组合数的几种算法详见组合数 - 预处理二维前缀和:
由于多组测试数据, 仅仅预处理出组合数是不够的, 必须预处理所有的结果, 这样对于每次询问都达到\(O(1)\)
运用动态规划的思想(也可以理解为二维前缀和):
状态表示:
\(dp[i][j]\): \(C^{0 - j}_{0 - i}\)中满足整除\(k\)的集合
属性: 数量
状态计算:
\(dp[i - 1][j]\): \(C^{0 - j}_{0 - (i - 1)}\)中满足整除\(k\)的集合
\(dp[i][j - 1]\): \(C^{0 - (j - 1)}_{0 - i}\)中满足整除\(k\)的集合
以上两部分有重合:
\(dp[i - 1][j - 1]\): \(C^{0 - (j - 1)}_{0 - (i - 1)}\)中满足整除\(k\)的集合
得到递推式:
\[dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]
\]
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010;
int t, k;
int c[N][N];
int res[N][N];
void init()
{
for (int i = 0; i < N; i ++ ){
for (int j = 0; j <= i; j ++ )
if (j == 0)
c[i][j] = 1;
else{
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % k;
res[i][j] = res[i - 1][j] + res[i][j - 1] - res[i - 1][j - 1];
if (c[i][j] == 0)
res[i][j]++;
}
res[i][i + 1] = res[i][i]; //向下一层传递当前总数
}
}
void solve()
{
int n, m;
scanf("%d%d", &n, &m);
m = min(m, n);
cout << res[n][m] << endl;
return;
}
int main()
{
cin >> t >> k;
init();
while (t -- )
solve();
return 0;
}
E题 a math problem(hard version)
思路:
\(l, r\)范围均在\(1 - 1e18\), 暴力枚举肯定会超时.
应用容斥原理(原理详见:容斥原理)
对于每个数\(x\), 我们对其分解质因数, 分解得到\(p_{1}, p_{2}...p_{m}\), 共计\(m\)个质因数, 问题转化为:
\(l-r\)的所有数中, 有多少数能够至少被这\(m\)个质数中的一个整除.(详见容斥原理中的例题)
那么解决该问题只需两步即可:
- 将\(x\)分解质因数
- 分别对区间\([1, l- 1]\)和\([1,r]\)应用容斥原理, 结果相减即可.
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
// 容斥原理
long long s[100010];
int n = 0;
void divide(LL x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0){
s[n ++] = i;
while (x % i == 0)
x = x / i;
}
if (x > 1)
s[n ++] = x;
return;
}
LL solve(LL m)
{
LL ans = 0;
for (int i = 1; i < 1 << n; i ++ ){
LL t = 1; //当前二进制所表示的所有集合内的数字的质数之积
int cnt = 0; //当前二进制表示的集合个数
for (int j = 0; j < n; j ++ ){ //枚举当前二进制的每一位
if (i >> j & 1){
if (t * s[j] > m){ //当前集合内的数
t = -1;
break;
}
cnt++;
t = t * s[j];
}
}
if (t != -1){
if (cnt % 2)
ans += m / t;
else
ans -= m / t;
}
}
return m - ans;
}
int main()
{
long long l, r, x;
cin >> l >> r >> x;
divide(x); //分解质因数
cout << solve(r) - solve(l - 1) << endl;
return 0;
}
为什么这样做不会超时:
25以内的质数(9个):
2 3 5 7 11 13 17 19 23
它们的乘积:\(223092870>2e9\)
即对于任意的 \(x<2e9\) :不会超过\(9\)个不同的质因子
即:应用容斥原理时最多有\(9\)个集合,总共需要枚举的集合的组合方式有\(2^{9}\)种
对于每种组合方式, 我们枚举二进制的每一位时, 最多有\(9\)位
即时间复杂度大概在\(2 \times 9\times 2^{9}\)
F题: 矩阵变换
思路
题目数据范围不大, 直接根据题意模拟即可
代码
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
const int N = 510;
int n, m, k;
char s[N][N];
int a, b, c, d;
int op;
void turnup()
{
if ((c + d) % 2 == 0){
int t = (c + d) / 2;
for (int i = a; i <= b; i ++ ){
for (int j = c; j < t; j ++ ){
char cs = s[i][j];
s[i][j] = s[i][2 * t - j];
s[i][2 * t - j] = cs;
}
}
}else{
int t = (c + d) / 2;
for (int i = a; i <= b; i ++ )
for (int j = c; j <= t; j ++ ){
char cs = s[i][j];
s[i][j] = s[i][2 * t + 1 - j];
s[i][2 * t + 1 - j] = cs;
}
}
}
void turndown()
{
if ((a + b) % 2 == 0){
int t = (a + b) / 2;
for (int i = c; i <= d; i ++ ){
for (int j = a; j < t; j ++ ){
char cs = s[j][i];
s[j][i] = s[2 * t - j][i];
s[2 * t - j][i] = cs;
}
}
}else{
int t = (a + b) / 2;
for (int i = c; i <= d; i ++ )
for (int j = a; j <= t; j ++ ){
char cs = s[j][i];
s[j][i] = s[2 * t + 1 - j][i];
s[2 * t + 1 - j][i] = cs;
}
}
}
void solve()
{
cin >> op >> a >> b >> c >> d;
if (op == 1){
turnup();
}else{
turndown();
}
for (int i = 1; i <= n; i ++ ){
cout << s[i] + 1;
puts("");
}
puts("");
return;
}
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ )
cin >> s[i] + 1;
while (k--)
solve();
return 0;
}