1.珠心算测试
算法分析
有人可能会误读题意。题意说的是,有多少个数,能够由集合中的另外两个数构成。不是说集合中的数a、b、c,有多少对能组成等式“a + b = c”。下面数据:
5
1 2 3 4 5
答案是3,不是4。
枚举每一个数,然后再枚举集合中的数,判断能否构成,一旦找到break退出。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
int a[110];
int main()
{
int n,k,sum=0;
cin>>n;
for (int i=1;i<=n;++i)
cin>>a[i];
for (int k = 1; k <= n; ++k) // 枚举每一个数字
{
int ok = 0;
for (int i = 1; i < n; ++i)
{
for (int j = i + 1; j <= n; ++j)
if (a[k] == a[i] + a[j])
{
++sum;
ok = 1;
break;
}
if (ok == 1) break;
}
}
cout<<sum<<endl;
return 0;
}
算法拓展
普及组的比赛中经常会用到标记思想,有时也叫桶的思想或hash思想。
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int a[110], f[20010], vis[20010]; //
int main()
{
// P2141 [NOIP2014 普及组] 珠心算测验
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
f[a[i]] = 1; // 核心
}
// 统计
int ans = 0;
for (int i = 1; i < n; ++i)
for (int j = i + 1; j <= n; ++j)
{
int t = a[i] + a[j];
if (vis[t] == 0)
{
if (f[t] == 1) ++ans;
vis[t] = 1;
}
}
printf("%d\n", ans);
return 0;
}
2.比例简化
算法分析
L L L很小,直接枚举 A ′ A' A′和 B ′ B' B′。要满足 A ′ A' A′和 B ′ B' B′互质、 A ′ / B ′ > = A / B A'/B' >= A / B A′/B′>=A/B的情况下 A ′ / B ′ A'/B' A′/B′最小。用 a n s a ansa ansa和 a n s b ansb ansb记录结果。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#define ll long long
using namespace std;
int A, B, L;
int gcd(int a, int b)
{
return b ? gcd(b, a%b) : a;
}
int main()
{
scanf("%d%d%d", &A, &B, &L);
double val = A * 1.0 / B;
int ansa, ansb;
double minval = 1e8;
for (int a = 1; a <= L; ++a)
for (int b = 1; b <= L; ++b)
if (gcd(a, b) == 1 && a * 1.0 / b >= val)
{
if (a * 1.0 / b < minval)
{
minval = a * 1.0 / b;
ansa = a; ansb = b;
}
}
printf("%d %d\n", ansa, ansb);
return 0;
}
3.螺旋矩阵
算法分析
n n n最大值为30000,暴力枚举肯定超时,过不了全部数据。思考发现,矩阵是一圈圈的,外圈比内圈多了8个数字。如果能直接定位 ( i , j ) (i, j) (i,j)处在第几个圈上,比如处在第 t t t圈,利用前缀和能直接得出前 t − 1 t-1 t−1圈的数字个数。然后判断 ( i , j ) (i, j) (i,j)处在第 t t t圈的哪条边上,可以快速计算。
如果 n n n是偶数,最后一圈的数字个数是4,如果 n n n是奇数,最后一圈的数字个数是1。求前缀和的时候,得讨论 n n n的奇偶性。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
ll sum[30010];
int n, x, y;
struct node
{
int zc, bc; // zc:周长 bc:边长
}a[30010];
int main()
{
scanf("%d%d%d", &n, &x, &y);
int s = n, sn;
if (n % 2) sn = n / 2 + 1; // sn:圈数
else sn = n / 2;
for (int i = 1; i < sn; ++i)
{
a[i].zc = s * 4 - 4;
a[i].bc = s;
s -= 2;
}
if (n % 2) a[sn].zc = 1, a[sn].bc = 1; else a[sn].zc = 4, a[sn].bc = 2;
sum[1] = a[1].zc;
for (int i = 2; i <= sn; ++i) sum[i] = sum[i-1] + a[i].zc;
int t = min(x, min(y, min(n + 1 - x, n + 1 - y)));
// (x, y)处在第t圈
ll ans = sum[t-1];
int k = a[t].bc; // 第t圈的边长
if (x == t)
{
ans += y - t + 1;
}else if (n + 1 - x == t)
{
ans += 2 * k - 1 + k - (y - t + 1);
}else if (y == t)
{
ans += 3 * k - 2 + k - (x - t + 1);
}else if (n + 1 - y == t)
{
ans += k + x - t;
}
printf("%lld\n", ans);
return 0;
}
4.子矩阵
算法分析
最直接的想法是dfs套dfs。先搜出 r r r行,再搜出 c c c列,统计比较结果。以下暴力算法能得70分。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, b[20][20];
int ans = 1e8;
void dfsc(int d, int sum)
{
if (sum > ans) return;
for (int i = lie[d-1] + 1; i <= m; ++i)
{
if (d - 1 + m - d + 1 < c) break;
lie[d] = i;
int s1 = 0, s2 = 0;
for (int k = 2; k <= r; ++k) s1 += abs(a[ hang[k] ][ lie[d] ] - a[ hang[k-1] ][ lie[d] ]);
for (int k = 1; k <= r; ++k) s2 += abs(a[ hang[k] ][ lie[d] ] - a[ hang[k] ][ lie[d-1] ]);
if (d == 1)
{
sum += s1;
}else
{
sum += s1 + s2;
}
if (d == c) ans = min(ans, sum);
else dfsc(d + 1, sum);
if (d == 1)
{
sum -= s1;
}else
{
sum -= s1 + s2;
}
}
}
void dfsr(int d)
{
for (int i = hang[d-1] + 1; i <= n; ++i)
{
if (d - 1 + n - d + 1 < r) break;
hang[d] = i;
if (d == r) dfsc(1, 0);
else dfsr(d + 1);
}
}
int main()
{
scanf("%d%d%d%d", &n, &m, &r, &c);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
dfsr(1);
printf("%d\n", ans);
return 0;
}
以上代码dfsc中,计算 s 1 s1 s1和 s 2 s2 s2的过程每次都要重复计算,造成了时间的浪费。可以预处理出来这些值。设:
h
v
a
l
[
i
]
[
x
]
[
j
]
hval[i][x][j]
hval[i][x][j]:第
i
i
i行和第
x
x
x行在第
j
j
j列上的数值之差的绝对值。
l
v
a
l
[
j
]
[
x
]
[
i
]
lval[j][x][i]
lval[j][x][i]:第
j
j
j列和第
x
x
x列在第
i
i
i行上的数值之差的绝对值。
l
i
e
s
u
m
[
j
]
:
liesum[j]:
liesum[j]:每列的所有相邻行的数值之差的绝对值和。
h
a
n
g
s
u
m
[
j
]
[
x
]
:
hangsum[j][x]:
hangsum[j][x]:在所有行上第
j
j
j列和第
x
x
x列之间的数值之差的绝对值之和。
搜行的时候,每搜出一行,就累加 l i e s u m [ j ] liesum[j] liesum[j]和 h a n g s u m [ j ] [ x ] hangsum[j][x] hangsum[j][x]。在搜列的时候,直接使用就行了。这样优化,能得80分。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int ans = 1e8;
void dfsc(int d, int sum)
{
if (sum > ans) return;
for (int j = lie[d-1] + 1; j <= m; ++j)
{
if (d - 1 + m - d + 1 < c) break;
lie[d] = j;
sum += liesum[j] + hangsum[lie[d-1]][j];
if (d == c) ans = min(ans, sum);
else dfsc(d + 1, sum);
sum -= liesum[j] + hangsum[lie[d-1]][j];
}
}
void dfsr(int d)
{
for (int i = hang[d-1] + 1; i <= n; ++i)
{
if (d - 1 + n - d + 1 < r) break;
hang[d] = i;
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] += lval[j][x][i];
if (d == r) dfsc(1, 0);
else dfsr(d + 1);
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] -= lval[j][x][i];
}
}
int main()
{
scanf("%d%d%d%d", &n, &m, &r, &c);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
// hval
for (int i = 1; i < n; ++i)
for (int x = i + 1; x <= n; ++x)
for (int j = 1; j <= m; ++j)
hval[i][x][j] = abs(a[i][j] - a[x][j]);
// lval
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
for (int i = 1; i <= n; ++i)
lval[j][x][i] = abs(a[i][j] - a[i][x]);
dfsr(1);
printf("%d\n", ans);
return 0;
}
翻看其他人的博客,发现有用以上算法过了的。不同的地方在 d f s c dfsc dfsc的实现。上面代码中用 l i e lie lie数组存储已经搜索过的每一列,这样每次调用的时候,造成了额外开销。把搜索过的信息保存在参数列表中,就会快很多。这样就能过了。以下代码重点看 d f s c dfsc dfsc的实现,100分。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int ans = 1e8;
void dfsc(int x, int t, int sum) // 上一个是x,共t个,累计值为sum,用lie[]存的话,有几个点超时
{
if (t == c)
{
ans = sum; return;
}
for (int j = x + 1; j <= m; ++j)
{
if (t + m - j + 1 < c) break;
int s = 0;
s = sum + liesum[j] + hangsum[x][j];
if (s < ans) dfsc(j, t + 1, s);
}
}
void dfsr(int d)
{
for (int i = hang[d-1] + 1; i <= n; ++i)
{
if (d - 1 + n - d + 1 < r) break;
hang[d] = i;
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] += lval[j][x][i];
if (d == r) dfsc(0, 0, 0);
else dfsr(d + 1);
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] -= lval[j][x][i];
}
}
int main()
{
scanf("%d%d%d%d", &n, &m, &r, &c);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
// hval
for (int i = 1; i < n; ++i)
for (int x = i + 1; x <= n; ++x)
for (int j = 1; j <= m; ++j)
hval[i][x][j] = abs(a[i][j] - a[x][j]);
// lval
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
for (int i = 1; i <= n; ++i)
lval[j][x][i] = abs(a[i][j] - a[i][x]);
dfsr(1);
printf("%d\n", ans);
return 0;
}
算法拓展
dfs+dp。
在搜完行的时候,我们用 l i e s u m liesum liesum记录了每列的所有相邻行的数值之差的绝对值和。下面的思路就是压行。将所有行在每列上的信息看作 l i e s u m liesum liesum数组的一个点。下面就是在该数列上选择 c c c个点,使值最小。问题就化成了dp了。
f [ i ] [ j ] f[i][j] f[i][j]:规划到了第 i i i列,总共取了 j j j列,且第 i i i列必取的最小值。
void swork()
{
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= m; ++i) f[i][1] = liesum[i];
for (int i = 2; i <= m; ++i)
for (int j = 2; j <= i; ++j)
for (int k = j - 1; k <= i - 1; ++k)
f[i][j] = min(f[i][j], f[k][j-1] + liesum[i] + hangsum[k][i]);
for (int j = c; j <= m; ++j) ans = min(ans, f[j][c]);
}
完整代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
int a[20][20], hang[20], lie[20], n, m, r, c, hval[20][20][20], lval[20][20][20];
int liesum[20], hangsum[20][20];
int f[20][20]; // f[i][j]:规划到了第i列,总共取了j列,且第i列必取
int ans = 1e8;
void swork()
{
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= m; ++i) f[i][1] = liesum[i];
for (int i = 2; i <= m; ++i)
for (int j = 2; j <= i; ++j)
for (int k = j - 1; k <= i - 1; ++k)
f[i][j] = min(f[i][j], f[k][j-1] + liesum[i] + hangsum[k][i]);
for (int j = c; j <= m; ++j) ans = min(ans, f[j][c]);
}
void dfsr(int d)
{
for (int i = hang[d-1] + 1; i <= n; ++i)
{
if (d - 1 + n - d + 1 < r) break;
hang[d] = i;
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] += hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] += lval[j][x][i];
if (d == r) swork();
else dfsr(d + 1);
if (d != 1)
{
for (int j = 1; j <= m; ++j) liesum[j] -= hval[ hang[d-1] ][i][j];
}
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
hangsum[j][x] -= lval[j][x][i];
}
}
int main() // dfs + dp
{
scanf("%d%d%d%d", &n, &m, &r, &c);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
// hval
for (int i = 1; i < n; ++i)
for (int x = i + 1; x <= n; ++x)
for (int j = 1; j <= m; ++j)
hval[i][x][j] = abs(a[i][j] - a[x][j]);
// lval
for (int j = 1; j < m; ++j)
for (int x = j + 1; x <= m; ++x)
for (int i = 1; i <= n; ++i)
lval[j][x][i] = abs(a[i][j] - a[i][x]);
dfsr(1);
printf("%d\n", ans);
return 0;
}