分治
知识点
-
算法思想
- 分:将原问题分解为k个规模较小的子问题(k≥2)
- 治:递归求解各子问题
- 合:合并子问题的解得到原问题解
-
适用条件
- 问题可分解:原问题能划分为相同结构的子问题
- 子问题独立:子问题间无重叠(区别于动态规划)
- 解可合并:子问题解能有效合并为最终解
练习题
P1010 [NOIP 1998 普及组] 幂次方
题目描述
任何一个正整数都可以用 \(2\) 的幂次方表示。例如 $137=27+23+2^0 $。
同时约定次方用括号来表示,即 \(a^b\) 可表示为 \(a(b)\)。
由此可知,\(137\) 可表示为 \(2(7)+2(3)+2(0)\)。
进一步:
\(7= 2^2+2+2^0\) ( \(2^1\) 用 \(2\) 表示),并且 \(3=2+2^0\)。
所以最后 \(137\) 可表示为 \(2(2(2)+2+2(0))+2(2+2(0))+2(0)\)。
又如 \(1315=2^{10} +2^8 +2^5 +2+1\)。
所以 \(1315\) 最后可表示为 \(2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)\)。
输入格式
一行一个正整数 \(n\)。
输出格式
符合约定的 \(n\) 的 \(0, 2\) 表示(在表示中不能有空格)。
输入输出样例 #1
输入 #1
1315
输出 #1
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
说明/提示
【数据范围】
对于 \(100\%\) 的数据,\(1 \le n \le 2 \times {10}^4\)。
NOIP1998 普及组 第三题
解题思路
对最后生成的式子进行分析,发现只包含0和2两个数字,并且只由\(2(0)、2、2+2(0)、2(2)\)四个数字组合而成,分别对应十进制下的\(0、1、2、3\)
所以对输入数据进行二进制拆分、并且是从大到小开始拆,进行分治求解每一部分的表示方式,最后递归到一起获得最终式子。
对于给定的例子: 2的10次方可表示为2(10),再对括号中的10进行拆分,拆成\(2(3)+2\),再对括号中的\(3\)进行拆分,拆成\(2+2(0)\)
最后再合并起来就是\(2(2(2+2(0))+2)\)
参考代码
//p1010
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
typedef pair<int, int> PII;
bool flag[25], flag1[25];
string print_set(int num){
if(num == 1) return "2(0)";
else if(num == 2) return "2";
else if(num == 3) return "2+2(0)";
else if(num == 4) return "2(2)";
else{
int value = 32768;
int zs = 15;
while(value > num){
value /= 2;
zs --;
}
if(num == value) return "2(" + print_set(zs) + ")";
else return "2(" + print_set(zs) + ")+" + print_set(num - value);
}
}
int main(){
int n;
cin >> n;
cout << print_set(n) << endl;
return 0;
}
P3612 [USACO17JAN] Secret Cow Code S
题目描述
奶牛们正在实验秘密代码,并设计了一种方法用于生成无限长度的字符串,作为他们代码的一部分。
给定一个字符串 \(s\),令 \(F(s)\) 为 \(s\) 后接 \(s\) 向右“旋转”一个字符的结果(在右旋转中,\(s\) 的最后一个字符旋转并成为新的第一个字符)。给定初始字符串 \(s\),奶牛们通过重复应用 \(F\) 来构建他们的无限长度代码字符串;因此每一步都会使当前字符串的长度翻倍。
给定初始字符串和一个索引 \(N\),请帮助奶牛计算无限代码字符串中第 \(N\) 个位置的字符。
输入格式
输入由一行组成,包含一个字符串和 \(N\)。字符串最多由 30 个大写字母组成,且 \(N \leq 10^{18}\)。
请注意,\(N\) 可能太大,无法放入标准的 32 位整数中,因此你可能需要使用 64 位整数类型(例如,C/C++ 中的 "long long")。
输出格式
请输出从初始字符串构建的无限代码字符串的第 \(N\) 个字符。第一个字符的位置为 \(N=1\)。
输入输出样例 #1
输入 #1
COW 8
输出 #1
C
说明/提示
在这个例子中,初始字符串 COW 按以下方式扩展:
COW -> COWWCO -> COWWCOOCOWWC
解题思路
考虑暴力求解,\(N<=10^{18}\),会TLE
考虑逆向求解,求该无限字符串的第N个位置的字符。设其复制次数为\(t\),那么可以在\(O(t)\)的时间复杂度内求解得到在原字符串的位置,输出该位置的字符即可。
具体求解方法为:先通过倍增获得当前无限字符串拥有第\(N\)个位置时候的长度\(len\)。如果\(N\)为\(len/2+1\),那么其代表的字符在上一个复制序列中处于最末尾的位置。否则,在原序列中位于\(N-len/2+1\)的位置。以此规则不断向前倒推,最后即可获得在原字符串中的位置。
参考代码
//p3612
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
typedef long long ll;
ll n;
string s;
int main(){
cin >> s >> n;
ll len = s.length();
ll val = len;
while(val < n) val *= 2;
while(n > len){
while(val >= n) val /= 2;
if(n == val + 1) n = val;
else n = n - val - 1;
}
cout << s[n-1] << endl;
return 0;
}
P1928 外星密码
题目描述
有了防护伞,并不能完全避免 2012 的灾难。地球防卫小队决定去求助外星种族的帮助。经过很长时间的努力,小队终于收到了外星生命的回信。但是外星人发过来的却是一串密码。只有解开密码,才能知道外星人给的准确回复。解开密码的第一道工序就是解压缩密码,外星人对于连续的若干个相同的子串 \(\texttt{X}\) 会压缩为 \(\texttt{[DX]}\) 的形式(\(D\) 是一个整数且 \(1\leq D\leq99\)),比如说字符串 \(\texttt{CBCBCBCB}\) 就压缩为 \(\texttt{[4CB]}\) 或者\(\texttt{[2[2CB]]}\),类似于后面这种压缩之后再压缩的称为二重压缩。如果是 \(\texttt{[2[2[2CB]]]}\) 则是三重的。现在我们给你外星人发送的密码,请你对其进行解压缩。
输入格式
输入一行,一个字符串,表示外星人发送的密码。
输出格式
输出一行,一个字符串,表示解压缩后的结果。
输入输出样例 #1
输入 #1
AC[3FUN]
输出 #1
ACFUNFUNFUN
说明/提示
【数据范围】
对于 \(50\%\) 的数据:解压后的字符串长度在 \(1000\) 以内,最多只有三重压缩。
对于 \(100\%\) 的数据:解压后的字符串长度在 \(20000\) 以内,最多只有十重压缩。保证只包含数字、大写字母、[ 和 ]。
解题思路
一道数据有一、、恶心的题目TAT,而且感觉代码写得不是很优雅
考虑题目逻辑。一旦遇到了符号\([\) ,证明存在压缩字符串,需要对字符串进行解压缩处理。
解压缩函数\(pr()\)的逻辑为:进入该函数,证明遇到了符号 '[' ,并且该符号后跟着数字,数字后跟着大写字母,大写字母后跟着符号']'。
但是符号'[]'内可能存在多重压缩如AC[3[3FUN]],因此可能存在迭代调用\(pr()\)函数的情况。
还有可能存在压缩字符串中存在压缩和非压缩字符串如[3A[2B]C]。
解决方法为加一个外层while循环,判断对于当前的' [ '一定要找到一个与之匹配的' ] ',找到需要重复的字符串,乘上' [ '后的次数,才代表对于当前解压缩任务,完整地返回了原本的字符串。
参考代码
//p1928
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
string s;
int idx;
string pr(){
//处理当前字符串
string temp = "";
string temp1 = "";
//处理乘的次数
int now = 0;
while(s[idx] != ']'){
while('0' <= s[idx] && s[idx] <= '9'){
now *= 10;
now += (s[idx] - '0');
idx ++;
}
while('A' <= s[idx] && s[idx] <= 'Z'){
temp += s[idx];
idx ++;
}
if(s[idx] == '['){
idx ++;
temp += pr();
}
}
if(now == 0) temp1 += temp;
for(int i = 1; i <= now; i++) temp1 += temp;
idx ++;
return temp1;
}
int main(){
cin >> s;
int len = int(s.length());
while(idx < len){
if(s[idx] == '['){
idx ++;
cout << pr();
}
else if(s[idx] == ']') idx ++;
else{
cout << s[idx];
idx ++;
}
}
cout << endl;
return 0;
}
P1498 南蛮图腾
题目描述
自从到了南蛮之地,孔明不仅把孟获收拾的服服帖帖,而且还发现了不少少数民族的智慧,他发现少数民族的图腾往往有着一种分形的效果,在得到了酋长的传授后,孔明掌握了不少绘图技术,但唯独不会画他们的图腾,于是他找上了你的爷爷的爷爷的爷爷的爷爷……帮忙,作为一个好孙子的孙子的孙子的孙子……你能做到吗?
给定一个正整数 \(n\),参考输出样例,输出图形。
输入格式
每个数据输入一个正整数 \(n\),表示图腾的大小(此大小非彼大小)
输出格式
这个大小的图腾
输入输出样例 #1
输入 #1
2
输出 #1
/\
/__\
/\ /\
/__\/__\
输入输出样例 #2
输入 #2
3
输出 #2
/\
/__\
/\ /\
/__\/__\
/\ /\
/__\ /__\
/\ /\ /\ /\
/__\/__\/__\/__\
说明/提示
数据保证,\(1 \leq n \leq 10\)。
解题思路
输出三角形时,最重要的问题在于确定其左下角的位置。
因此在函数中的参数设置:
1)当前三角形左下角坐标\((x,y)\)
2)确定当前三角形的长和宽\(w、h\)以及当前还需要迭代的次数\(cs\)
3)确定迭代的三个小三角形的位置,分别为\((x,y)、(x, y+w/2)、(x-h/2,y+w/4)\)
4)当\(cs==1\)时,即可以确定最小单位三角形的左下角坐标,向字符数组写入,最后遍历数组输出。
参考代码
//p1498
#include <stdio.h>
#include <iostream>
using namespace std;
char tri[1000][1000];
//输入确定三角形的左下角点的坐标
void gz(int x, int y, int cs, int w, int h){
if(cs == 1){
tri[x][y] = '/';
tri[x][y+1] = '_';
tri[x][y+2] = '_';
tri[x][y+3] = '\\';
tri[x-1][y+1] = '/';
tri[x-1][y+2] = '\\';
return;
}
gz(x, y, cs - 1, w / 2, h / 2);
gz(x, y + w / 2, cs - 1, w / 2, h / 2);
gz(x - h / 2, y + w / 4, cs - 1, w / 2, h / 2);
}
int main(){
int n;
cin >> n;
int x_pos = 2, y_pos = 4;
for(int i = 1; i < n; i++){
x_pos *= 2;
y_pos *= 2;
}
gz(x_pos, 1 , n, y_pos, x_pos);
for(int i = 1; i <= x_pos; i++){
for(int j = 1; j <= y_pos; j++){
if(!tri[i][j]) cout << " ";
else cout << tri[i][j];
}
cout << endl;
}
return 0;
}
P4141 消失之物
题目描述
ftiasch 有 \(n\) 个物品, 体积分别是 \(w_1,w_2,\dots,w_n\)。由于她的疏忽,第 \(i\) 个物品丢失了。
“要使用剩下的 \(n-1\) 物品装满容积为 \(x\) 的背包,有几种方法呢?”——这是经典的问题了。
她把答案记为 \(\text{cnt}(i,x)\) ,想要得到所有\(i \in [1,n]\), \(x \in [1,m]\) 的 \(\text{cnt}(i,x)\) 表格。
输入格式
第一行两个整数 \(n,m\),表示物品的数量和最大的容积。
第二行 \(n\) 个整数 \(w_1,w_2,\dots,w_n\),表示每个物品的体积。
输出格式
输出一个 \(n \times m\) 的矩阵,表示 \(\text{cnt}(i,x)\) 的末位数字。
输入输出样例 #1
输入 #1
3 2
1 1 2
输出 #1
11
11
21
说明/提示
【数据范围】
对于 \(100\%\) 的数据,\(1\le n,m \le 2000\),且 \(1\le v_i\le m\)。
【样例解释】
如果物品 3 丢失的话,只有一种方法装满容量是 2 的背包,即选择物品 1 和物品 2。
解题思路
题目第一眼看上去是背包,特殊的地方在于输出的矩阵\(cnt[i][j]\)表示当第\(i\)个物品消失,只剩下其余的\(n-1\)个物品时,凑成容积为\(j\)的情况方案数(末位数字,注意对10的取模运算)。
考虑暴力解法,如果把第\(i\)个物品排除之后生成一个新序列,对该序列进行01背包问题求解,那么总时间复杂度为\(O(n^2m)\),因为\(n,m<=2000\),总时间复杂度会达到\(O(10^{10})\)数量级。因此考虑对第一个\(n\)进行优化。
简化题目所给条件,先不考虑第\(i\)个物品不选,考虑所有物品都能选的情况下,用数组\(f[i]\)表示总体积为\(i\)时的方案数量,为正常01背包,时间复杂度为\(O(n*m)\)。
考虑不选第\(i\)个物品的情况:
- 当\(j<w[i]\)时,第\(i\)个物品一定不会被选,此时\(cnt[i][j] = f[j]\);
- 当\(j>=w[i]\)时,第\(i\)个物品有可能被选上,总方案数需要扣掉选上第\(i\)个物品的方案数。逆向思维来想,选上了第\(i\)个物品,并且总体积为\(j\)的方案数为\(cnt[i][j-w[i]]\),也就是不选\(i\)且总体积为\(j-w[i]\)的方案数等价于选了\(i\)且总体积为\(j\)的方案数;
- 因此可以得出,\(cnt[i][j]=f[j]-cnt[i][j-w[i]]\),其时间复杂度为\(O(nm)\)。并且cnt数组只有对该行有效,可以压缩成一维。
参考代码
//p4141
#include <stdio.h>
#include <iostream>
using namespace std;
typedef long long ll;
ll w[2005], f[2005], cnt[2005];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> w[i];
f[0] = 1;
for(int i = 1; i <= n; i++){
for(int j = m; j >= 0; j--){
if(j >= w[i]) f[j] = (f[j] + f[j-w[i]]) % 10;
}
}
cnt[0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(j < w[i]) cnt[j] = f[j];
else cnt[j] = (f[j] - cnt[j - w[i]] + 10) % 10;
cout << cnt[j] % 10;
}
cout << endl;
}
return 0;
}

浙公网安备 33010602011771号