计算智能 士兵分治

SCAU 计算智能 里面有几题分治,特意做一个总结。

 

1142 巡逻的士兵

Description

有N个士兵站成一队列, 现在需要选择几个士兵派去侦察。
为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士兵, 
或者是去除掉所有站立位置为偶数的士兵。直到不超过三个战士,他们将被送去侦察。现要求统计按这样的方法,
总共可能有多少种不同的正好三个士兵去侦察的士兵组合方案。

注: 按上法得到少于三士兵的情况不统计。

1 <= N <= 2的32次方-1

 

输入格式

有多行(可能有上百行,尽量优化代码),每行一个数字N,最后一行是0

 

输出格式

对每一行的数字N,输出针对N的方案数

直到没有数字

 

输入样例

10
4
0

输出样例

2
0

思路:

已知,每次去除队列中的奇数或者是偶数位置的士兵,根据题意总共有3种情况,最后人数n==3的时候是一个合法的结果,n<3 的时候是一个不合法的结果,当n>3我们继续分就好了。

那么还有一个n的奇偶性的问题,如果是偶数,去除奇数和偶数都是去除一半的人,人数一样,结果直接乘2就好了,如果是奇数,一半是n/2,另一半就是n/2+1,相加就好。

代码:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

int dc(int n) {
	if (n == 3)return 1;
	if (n < 3)return 0;
	if (n % 2 == 0)return 2 * dc(n / 2);
	else { return dc(n / 2) + dc(n / 2 + 1); }
}
int main() {
	ios::sync_with_stdio(false);
	int n; cin >> n;
	while (n) {
		cout << dc(n)<<endl;
		cin >> n;
	}


}

18441 偷懒的士兵

Description

有N个士兵站成一队列, 现在需要选择几个士兵派去侦察。
为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士兵, 
或者是去除掉所有站立位置为偶数的士兵。直到不超过三个战士,他们将被送去侦察。现有一个“聪明”的士兵,
经常通过选择站在合适的初始位置,成功避免被选中去侦察。这引起了陈教官的注意。陈教官希望你编写一个程序,
当给定士兵数之后,输出有多少个位置上的士兵是不可能被选中去巡逻的。

注: 按上法得到少于三士兵的情况不用去巡逻。

1 <= N <= 21亿

输入格式

有多行(可能有上百行,请尽量优化代码),每行一个数字N,最后一行是0

输出格式

对每一行的数字N,不可能被选中去巡逻的位置数

直到没有数字

 

输入样例

10
6
0

输出样例

4
0

思路:

这个题和上面那一题思路绝大部分都一样,只有一点需要变。

当最后n==3时,全部士兵都要去巡逻,自然不可能被选中的位置数为0;

当n<3时,不可能被选择的数量为n。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

int dc(int n) {
	if (n == 3)return 0;
	if (n < 3)return n;
	if (n % 2 == 0)return 2 * dc(n / 2);
	else { return dc(n / 2) + dc(n / 2 + 1); }
}
int main() {
	ios::sync_with_stdio(false);
	int n; cin >> n;
	while (n) {
		cout << dc(n)<<endl;
		cin >> n;
	}


}

18442 偷懒的士兵2

Description

有N个士兵站成一队列, 现在需要选择几个士兵派去侦察。
为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士兵, 
或者是去除掉所有站立位置为偶数的士兵。直到不超过三个战士,他们将被送去侦察。现有一个“聪明”的士兵,
经常通过选择站在合适的初始位置,成功避免被选中去侦察。这引起了陈教官的注意。陈教官希望你编写一个程序,
当给定士兵数之后,输出不可能被选中去巡逻的最少编号位置(如果不存在不可能被选中的位置,则输出0)。

注: 按上法得到少于三士兵的情况不用去巡逻。

1 <= N <= 100000

 

输入格式

有多行(不多于20行),每行一个数字N,最后一行是0

输出格式

对每一行的数字N,不可能被选中去巡逻的位置数

直到没有数字

 

输入样例

9
6
0

 

输出样例

2
0

思路:

总体思路还是一样的,但是我们的返回值转变为序列开头的编号。

我们可以这样思考,对于一个序列,n==3时,不可以偷懒,我们返回一个巨大值,n<3时可以偷懒,我们返回开头编号,n>3我们继续分。每次去除奇数或者偶数会有两个序列产生,最后一共会有两个返回值,我们取最小即可。

难点在于,如何记录去奇或去偶得到的序列的第一个位置的编号。通过观察和思考,我们可以发现原序列L,去除偶数后的序列L1,开头第一个的编号不变,而去除奇数后的序列L2,开头第一个的编号是原序列L第一个编号first+k;这个k是一个倍增的常数,k*=2;大概证明一下,因为每次我们都去除一半的数据,每一次相邻两个位置的编号间隔就会是之前的2倍。或者,脑补一颗完全二叉树图,理论上也可以找到对应关系,我也不会。。。。

值得一提的是,n的奇偶性呢,还要不要讨论?可以但没必要,原本讨论奇偶性就是为了减少二叉树的分支,来减少时间开销。

原本对于第一,第二题。无论n是奇数或者偶数,n>3时,我们都可以写成f((n+1)/2)+f(n/2);只是,

我们发现n是偶数的时候,分出的两个序列长度一样,故得到的结果也一样。只是第三题,尽管分出序列长度一样,开头的编号也不一样。

代码:

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#define MAX 2147483647
int dc(int n,int first,int step) {
	if (n == 3)return MAX;
	if (n < 3)return first;
	return min(dc((n + 1) / 2, first, step*2) , dc(n/2,first+step,step*2));
}
int main() {
	ios::sync_with_stdio(false);
	int n; cin >> n;
	while (n) {
		cout << dc(n, 1, 1) % MAX << endl;
		cin >> n;
	}


}

有错请指教!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值