Acm枚举 详细整理 [SCOI2005]扫雷 、Colorful Slimes、Atcoder Shorten Diameter

枚举

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[i1],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[n1]+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 , ⋯ &ThinSpace; , a i − k ) min(a_{i},a_{i-1},a_{i-2},\cdots,a_{i-k}) min(ai,ai1,ai2,,aik)
枚举 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);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值