枚举
acm做题的过程中,遇到枚举类型的题目,往往不是枚举所有的情况
枚举类型题目往往通过枚举关键值,而决定其他的值
在做枚举类型的题目时,寻找决定答案的关键值是最重要的
[SCOI2005]扫雷
题意
格子里没有雷,格子的数字表示周围八格雷的数量
现在棋盘是 n×2 的,第一列里面某些格子是雷,而第二列没有雷
由于第一列的雷可能有多种方案满足第二列的数,提问满足条件的第一列方案数
分析
这道题看似每个格子是否有雷都有0,1两种情况,然后
2
1
000
2^1000
21000的复杂度成功爆炸
但根据扫雷的规则,对于第二列的每个
b
[
i
]
b[i]
b[i],只要知道第一列
a
[
i
−
1
]
,
a
[
i
]
a[i-1],a[i]
a[i−1],a[i]就可以推出
a
[
i
+
1
]
a[i+1]
a[i+1],然后
b
[
i
+
1
]
b[i+1]
b[i+1]推出
a
[
i
+
2
]
a[i+2]
a[i+2]依次类推
所以我们只要枚举第一列
a
[
1
]
,
a
[
2
]
a[1],a[2]
a[1],a[2]即可推出所有
a
[
3
]
⋯
a
[
n
]
a[3]\cdots a[n]
a[3]⋯a[n],最后只需要验证
a
[
n
−
1
]
+
a
[
n
]
a[n-1]+a[n]
a[n−1]+a[n]是否等于
b
[
n
]
b[n]
b[n]即可
代码
#include <iostream>
#include <cstdio>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 10005;
int a[maxn], b[maxn], x[5][3] = { 0,0,0,
0,0,1,
0,1,0,
0,1,1 }; //枚举四种情况
int main() {
int n; scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &b[i]);
if (n == 1) {
printf("1\n");
return 0;
}
int cnt = 0;
for (int k = 1; k <= 4; k++) {
a[1] = x[k][1]; a[2] = x[k][2]; //只与a列前两个格子有关
if (a[1] + a[2] != b[1] || a[1] + a[2] > b[2])
continue;
int i;
for (i = 3; i <= n; i++) {
a[i] = b[i - 1] - a[i - 2] - a[i - 1];
if (a[i] < 0)break;
}
if (i <= n)continue;
if (a[n - 1] + a[n] == b[n])
cnt++;
}
printf("%d\n", cnt);
}
Atcoder Colorful Slimes
题意
有n种史莱姆,有两种操作
- 抓 i i i号史莱姆需要 a i a_{i} ai的时间
- 使所有拥有的史莱姆编号加一,第
n
n
n号则变为1,花费时间x
集齐所有史莱姆所需的时间
分析
若确定转换机器的次数
k
k
k,则所需时间确定
转换次数为
k
k
k时,抓第
i
i
i个史莱姆所需时间为
m
i
n
(
a
i
,
a
i
−
1
,
a
i
−
2
,
⋯
 
,
a
i
−
k
)
min(a_{i},a_{i-1},a_{i-2},\cdots,a_{i-k})
min(ai,ai−1,ai−2,⋯,ai−k)
枚举
k
k
k的次数即可找到最少花费的时间
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 2005;
LL a[maxn], m[maxn];
int main() {
LL n, cnt, sum = 0;
scanf("%lld%lld", &n, &cnt);
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
m[i] = a[i];
sum += a[i];
}
LL Min = sum; int p;
for (int k = 1; k < n; k++) { //枚举转换次数为k
for (int i = 0; i < n; i++) {
p = i - k < 0 ? i - k + n : i - k;
if (a[p] < m[i]) {
sum -= m[i] - a[p];
m[i] = a[p];
}
}
Min = min(Min, sum + cnt * k);
}
printf("%lld\n", Min);
}
Atcoder Shorten Diameter
题意
给出一棵树,要求删除一些点,使这棵树的直径不超过k(任意两点之间的路径经过的边数不超过k)
分析
若确定一棵树的中心,只保留离中心不超过
k
2
\frac{k}{2}
2k的点即可
若
k
k
k为奇数,则以边为中心,以边的两点为中心,只保留离两个点距离不超过
k
2
\frac{k}{2}
2k的点
如上分析,枚举树的中心即可
代码
#include <iostream>
#include <bitset>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 2005;
vector<int> E[maxn];
int vis[maxn], k, cnt, u[maxn], v[maxn];
void dfs(int x, int step) {
if (step == k + 1)
return;
for (int i = 0; i < E[x].size(); i++) {
if (vis[E[x][i]] == -1) {
vis[E[x][i]] = step;
cnt++;
dfs(E[x][i], step + 1);
}
else if (vis[E[x][i]] > step) {
vis[E[x][i]] = step;
dfs(E[x][i], step + 1);
}
}
}
int main() {
int n;
scanf("%d%d", &n, &k);
int ans = n;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u[i], &v[i]);
E[u[i]].push_back(v[i]);
E[v[i]].push_back(u[i]);
}
if (~k & 1) {
k >>= 1; //以点为中心
for (int i = 1; i <= n; i++) {
fill(vis, vis + n + 1, -1);
vis[i] = 0; cnt = 1;
dfs(i, 1);
ans = min(ans, n - cnt);
}
}
else {
k >>= 1; //以边为中心
for (int i = 1; i < n; i++) {
fill(vis, vis + n + 1, -1);
vis[u[i]] = vis[v[i]] = 0; cnt = 2;
dfs(u[i], 1); dfs(v[i], 1);
ans = min(ans, n - cnt);
}
}
printf("%d\n", ans);
}