P4155 [SCOI2015]国旗计划(倍增)

博客介绍了如何解决SCOI2015中的国旗计划问题,该问题涉及覆盖边防站的路径,利用倍增算法降低复杂度。文章详细阐述了问题背景、输入输出格式、思路以及AC代码,特别强调了区间预处理和搜索过程,最终实现了高效解决方案。

传送门:P4155

题目描述

A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 N 名优秀的边防战上作为这项计划的候选人。

A 国幅员辽阔,边境线上设有 M 个边防站,顺时针编号 11 至 M。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。N 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。

现在,国十安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。

输入格式

第一行,包含两个正整数 N,M,分别表示边防战士数量和边防站数量。

随后 N 行,每行包含两个正整数。其中第 i 行包含的两个正整数 _{C_{i}}^{D_{i}}分别表示 i 号边防战士常驻的两个边防站编号,_{C_{i}} 号边防站沿顺时针方向至 ​^{D_{i}} 号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。

输出格式

输出数据仅 1 行,需要包含 N 个正整数。其中,第 j 个正整数表示 j 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。

输入输出样例

输入 #1

4 8
2 5
4 7
6 1
7 3

输出 #1

3 3 4 3

说明/提示

N⩽2×^{10^{5}},M<109,1⩽_{C_{i}},^{D_{i}}⩽M。

思路

我们使用struct定义每个士兵的编号以及奔袭区间

接着题目中给到的是环(有点恶心)所以我们给他转成链(长度2M)

因为每个士兵的奔袭区间不会被完全包含,我们按照每位士兵的区间左端点l排序,就可以得到单调递增的区间

这样的话,我们可以枚举每个战士,但复杂度太高了

所以我们用倍增!

预处理每名战士:

从1号战士开始,一直到某一名战士的左端点大于这名战士的右端点,那么这名战士区间内奔袭过的最远的战士就是他

我们设lrr(i,j)表示第 i 个战士奔袭 2^{j} 步到达的边防站,转移公式就是

lrr(i,j)=lrr[lrr(i,j−1),j−1]

接着开始搜索:

lmt变量限制了区间长度(即s[x].l+m)

i 从 19 开始遍历,如果符合条件,即使 ans就增加2^{i}

然后更新cnt,x , 最后记录答案

----------------------------------------------------于是这就做完了!-----------------------------------------------------

AC代码:

#include<bits/stdc++.h>
using namespace std;
struct sldr{
	int l,r,id;
}s[400009];
int n,m,ans[200009],lrr[400009][29];
bool cmp(sldr a,sldr b){
	return a.l<b.l;
}
void init(){
	int cnt=1;
	for(int i=1;i<=2*n;i++){
		while(cnt<=2*n&&s[cnt].l<=s[i].r){//搜索直至找到当前右端点之后的左端点 
			cnt++;
		}
		int pos=cnt-1;
		lrr[i][0]=pos;
	}
	for(int i=1;i<20;i++){
		for(int j=1;j<=2*n;j++){
			lrr[j][i]=lrr[lrr[j][i-1]][i-1];
		}
	}
}
void srch_ans(int x){
	int limit=s[x].l+m,cnt=1,r=x;
	for(int i=19;i>=0;i--){
		if(lrr[x][i]>0&&s[lrr[x][i]].r<limit){
			cnt+=(1<<i);
			x=lrr[x][i];
		}
	}
	ans[s[r].id]=cnt+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);//使s变为l单调递增的序列 
	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;//将整个序列复制一遍(要满足第39~41行,即区间跨过点1的情况) 
	}
	init();
	for(int i=1;i<=n;i++){
		srch_ans(i);
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}

总结

通过暴力算法,可以推出满分算法

倍增的话……注意一下细节,找转移公式就好

### 关于SCOI2008 P2476 着色方案的题解 #### 动态规划与状态压缩的应用 此问题的核心在于动态规划 (Dynamic Programming, DP) 和状态压缩技术的结合应用。题目要求计算给定长度的颜色序列的不同染色方案数,其中某些位置可能已经预设颜色[^3]。 为了高效解决该问题,可以定义一个三维的状态转移方程 `dp[i][j][k]` 表示前 i 个格子被涂成 j 种不同颜色,并且第 i 个格子的颜色为 k 的情况下有多少种合法的染色方式。然而由于直接实现这种三维数组可能会超出内存限制,因此可以通过滚动数组优化或者进一步简化状态表示来降低空间复杂度[^3]。 另外一种更优美的方法是利用位运算来进行状态压缩。具体来说,我们可以用一个整数 S 来代表当前已经被使用的颜色集合(即每一位对应一种颜色),从而将原本复杂的多维 dp 数组转化为二维甚至一维的形式以便更好地管理资源并提高效率[^3]。 以下是基于上述思路的一个 Python 实现例子: ```python MOD = int(1e9 + 7) def solve(n, m, fixed_colors): # 初始化DP表 prev_dp = [0]*(1<<m) curr_dp = [0]*(1<<m) for mask in range(1 << m): count = bin(mask).count('1') if not any(fixed_colors[i]-1 & mask != 0 for i in range(len(fixed_colors))): prev_dp[mask] = pow(m-count,m-1)%MOD res= sum(prev_dp) % MOD return res # 输入处理部分省略... print(solve(n, m, fixed_colors)) ``` 以上代码片段展示了如何通过状态压缩技巧减少存储需求的同时完成有效的状态转移操作[^3]。 #### 注意事项 需要注意的是,在实际编码过程中还需要考虑边界条件以及模运算带来的影响等问题;此外对于大规模数据输入情况下的性能调优也是不可忽视的一环[^3]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值