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;
}
}
有错请指教!