倍增法的理解以及具体实现

# P4155[SCOI2015] 国旗计划
## 题目描述
A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 N 名优秀的边防战士作为这项计划的候选人。
A 国幅员辽阔,边境线上设有 M 个边防站,顺时针编号 1 至 M。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。N 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。
现在,国土安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。
## 输入格式
第一行,包含两个正整数 N, M,分别表示边防战士数量和边防站数量。
随后 N 行,每行包含两个正整数。其中第 i 行包含的两个正整数 C_i、D_i 分别表示 i 号边防战士常驻的两个边防站编号,C_i 号边防站沿顺时针方向至 D_i 号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。
## 输出格式
输出数据仅 1 行,需要包含 N 个正整数。其中,第 j 个正整数表示 j 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。
前言
1,首先来谈谈对倍增法的理解,我认为倍增法能实现的前提是任意十进制数都能转化成一个二进制数,实现的流程大概是,首先明确问题(这里的问题可以抽象化为一个十进制数n),再把这个n转化为二进制数b,之后再把b拆分为log2n个子问题,
这里的每一个子问题都代表了二进制数b的某一位,因此子问题之间的构成应该遵循从高位到低位的顺序,这样一来我们就把一个复杂度为O(n)的问题转化为了O(log2n)的问题,大大提高了效率。
2,针对这道题我们还用到了破环成链的技巧,即将原序列复制一份头尾相连,这样我们就可以把问题转化为一个线性问题,这样做的好处有很多比如我们在处理区间等问题时有了明确的左右只分简而言之就是保留了环的原有性质的同时方便我们进行操作。
#include
using namespace std;
int n;//问题所在
int m, res[200005], go[400005][20];
struct soldier {
	int id, l, r;
} s[400005];
int cmp(soldier a, soldier b)
{
	return a.l < b.l;
}
void search(int k){
	int lmt = s[k].l + m, ans = 1, p = k;
	for (int i = 19; i >= 0; i--) {//这里的每一个子问题都代表了二进制数b的某一位,因此子问题之间的构成应该遵循从高位到低位的顺序
		if (go[k][i] != 0 && s[go[k][i]].r < lmt) {//这里实际上就是在凑m的二进制数
			ans += (1 << i);
			k = go[k][i];
		}
	}
	res[s[p].id] = ans + 1;//这里加1是因为我们只计算了区间个数,而实际上还要加上起始的区间
}
int main(){
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {//破环成链
		cin >> s[i].l >> s[i].r;
		if (s[i].r < s[i].l)//我们在处理区间等问题时有了明确的左右只分
			s[i].r += m;
		s[i].id = i;
	}
	sort(s + 1, s + 1 + n, cmp);
	for (int i = 1; i <= n; i++) {//此处即复制过程
		s[i + n] = s[i];
		s[i + n].l = s[i].l + m;
		s[i + n].r = s[i].r + m;
	}
	//具体实现倍增法时我们会利用贪心的思想,先解决每一个区间的最优情况,再以此推出每两个区间的最优情况,以此类推4,8,16
	//这里是每一个区间的最优情况
	for (int i = 1, p = i; i <= 2 * n; i++) {
		while (p <= 2 * n && s[p].l <= s[i].r)
			p++;
		int pos = p - 1;//这里减一的原因是跳出while循环的p是不符合条件的,应该倒回前一个状态
		go[i][0] = pos;
	}
	//这里是递推的过程
	for (int i = 1; i < 20; i++) {
		for (int j = 1; j <= 2 * n; j++) {
			go[j][i] = go[go[j][i - 1]][i - 1];//递推公式,即go[j][i]代表了从j出发,走2^i步后的区间
		}
	}
	for (int i = 1; i <= n; i++)
		search(i);
	for (int i = 1; i <= n; i++)
		cout << res[i] << " ";
    return 0;
}

你可能感兴趣的:(算法学习,算法,数据结构)