4.13 题解

T1 战斗

由于怪物有清理 buff 机制,所以无法使用任何方法进行贪心。题目的数据范围只有 \(n \leq 20\),所以我们可以考虑暴力枚举所有情况,即每一个回合是使用攻击还是施加中毒,最终来找到一个最小值。

#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[21], b[21], c[21], ans = 1e9;
void dfs(int dep, int buff, int lose, int cnt){
	if(dep > n){
		return;
	}if(lose >= m){ // 怪兽已经失去的血量 
		ans = min(ans, dep);
		return;
	}
	lose += buff;
	int temp = cnt;
	if(lose + a[dep] >= c[cnt]){
		while(c[cnt] <= lose + a[dep]){
			cnt++;
		}
		dfs(dep + 1, 0, lose + a[dep], cnt);
	}else{
		dfs(dep + 1, buff, lose + a[dep], cnt);
	}
	cnt = temp;
	if(lose + b[dep] >= c[cnt]){
		while(c[cnt] <= lose + b[dep]){
			cnt++;
		}
		dfs(dep + 1, 0, lose + b[dep], cnt);
	}else{
		dfs(dep + 1, buff + b[dep], lose + b[dep], cnt);
	}
}
int main(){
	ans = 1e9;
	cin >> n >> m >> k;
	for(int i = 0; i < n; i++){
		cin >> a[i] >> b[i];
	}
	for(int i = 1; i <= k; i++){
		cin >> c[i];
	}
	c[k+1] = 1e9 + 10;
	dfs(0, 0, 0, 1);
	if(ans != 1e9)
		cout << ans << endl;
	else
		cout << -1 << endl;
}

T2 网格

发现询问的是两个格子是否连通,而且多次询问,每次要做到 \(O(1)\)

将相邻的、能互相联通的格子放在一个连通块里,查看两个点是否连通等价于查看两个点是否在一个连通块里。

具体方法:使用并查集对每个格子的相邻格子合并一下即可 。

考虑完查询操作,考虑增加障碍物怎么实现。

并查集有查询祖先操作,有合并操作,怎么删除无障碍物的格子(将其从连通块中删除出去)呢?

删除和合并在网格中就如一对反向操作一样,换个顺序就是另一个。

正序增加障碍物就相当于倒序删除障碍物。

将询问离线(记录)下来,倒序进行将有障碍物的格子变成无障碍物的格子就相当于 \(opt=1\) 时的情况了。

将有障碍物的格子变成无障碍物的格子就是将当前格子与相邻的无障碍物的格子合并。

复杂度:\(O(nm+t)\)

详见代码:

#include<bits/stdc++.h>
using namespace std;

const int MAXN=5e3+5,MAXT=1e7+5;
int n,m,q,t,r1[MAXT],c1[MAXT],r2[MAXT],c2[MAXT];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int num,num1,siz[MAXN][MAXN];
pair<int,int>f[MAXN][MAXN];//STL中的pair来储存x和y两个值 
bool b[MAXN][MAXN],ans[MAXT],opt[MAXT];
/*
f[i][j]存储祖先
siz[i][j]表示并查集大小
b[i][j]表示(i,j)上是否有障碍物 
ans[i]倒序储存答案 
*/ 
void init(){//赋初值 
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			f[i][j]=make_pair(i,j);
			siz[i][j]=1;
		}
	}
}

pair<int,int> find(int x,int y){//求祖先 
	if(f[x][y].fi==x&&f[x][y].se==y)return f[x][y];
	return f[x][y]=find(f[x][y].fi,f[x][y].se);//路径压缩 
}

void merge(int x,int y,int xx,int yy){//合并 
	pair<int,int>u=find(x,y),v=find(xx,yy);
	if(u==v)return;
	if(siz[u.fi][u.se]>siz[v.fi][v.se]){//按秩合并 
		f[v.fi][v.se]=u;
		siz[u.fi][u.se]+=siz[v.fi][v.se];
	}else{
		f[u.fi][u.se]=v;
		siz[v.fi][v.se]+=siz[u.fi][u.se];
	}
}

bool check(int x,int y,int xx,int yy){//查询(x,y)和(xx,yy)是否在一个连通块里 
	pair<int,int>u=find(x,y),v=find(xx,yy);
	if(u==v)return 1;
	return 0;
}

int main(){
	cin >> n >> m >> q;
	int x,y;
	init();
	for(int i=1;i<=q;++i){
		cin >> x >> y;
		b[x][y]=1;
	}
	t=read();
	for(int i=1;i<=t;++i){
		cin >> opt[i]
		if(!opt[i]){
            cin >> r1[i] >> c1[i] >> r2[i] >> c2[i];
			num++;
		}else{
            cin >> r1[i] >> c1[i];
			b[r1[i]][c1[i]]=1;//构造正序时最后时网格的样子 
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(b[i][j])continue;
			for(int k=0;k<4;++k){
				if(b[i+dx[k]][j+dy[k]])continue;
				if(i+dx[k]<1||i+dx[k]>n||j+dy[k]<1||j+dy[k]>m)continue;
				merge(i,j,i+dx[k],j+dy[k]);//按照正序时最后时网格的样子构建连通块 
			}
		}
	}
	for(int i=t;i>=1;--i){
		if(!opt[i]){
			if(check(r1[i],c1[i],r2[i],c2[i]))ans[num-num1]=1;//倒序无法直接输出,需要记录答案 
			num1++;
		}else{
			b[r1[i]][c1[i]]=0;
			for(int j=0;j<4;++j){
				if(b[r1[i]+dx[j]][c1[i]+dy[j]])continue;
				if(r1[i]+dx[j]<1||r1[i]+dx[j]>n||c1[i]+dy[j]<1||c1[i]+dy[j]>m)continue;
				merge(r1[i],c1[i],r1[i]+dx[j],c1[i]+dy[j]);//将这个变为无障碍物的格子与相邻格子合并 
			}
		}
	}
	for(int i=1;i<=num;++i){
		if(ans[i])puts("YES");
		else puts("NO");
	}
}

T3 公路

不超速的情况:由于车速的变动幅度有限,因此并不一定每个点的速度都能达到限速那么大。所以我们可以先将每个点 \(i\) 真实可以到达的速度 \(𝑅𝑒𝑎𝑙_𝑖\) 算出来。当某个点 \(i\) 满足 \(𝑅𝑒𝑎𝑙_𝑖 == 𝑙𝑖𝑚𝑖𝑡_𝑖\) 时(即第 \(i\) 点 的限速),我们认为这个点为“关键点”。

对于非关键点来说,本身的速度就因为其他限制而无法到达限速。 即使选择在这一点超速,也无法使得最后的结果更大。所以如果选择超速,一定是在这些关键点超速。如果关键点超速,那么它周围的点也会随之变化。 求出每个点的实际速度 \(𝑅𝑒𝑎𝑙_𝑖\) 的方法:可以考虑第 \(i\) 点会被三个 \(limit\) 所限制,一个是 \(𝑙𝑖𝑚𝑖𝑡_𝑖\),一个是 \(𝑅𝑒𝑎𝑙_{𝑖−1} + 1\),一个是 \(𝑅𝑒𝑎𝑙_{𝑖+1} + 1\),这个点的实际速度是这三个 \(limit\) 的最小值。所以我们可以正反各扫描一遍求出 \(𝑅𝑒𝑎𝑙_{𝑖−1}\)\(𝑅𝑒𝑎𝑙_{𝑖+1}\) 对此点的限制。 现在我们算出了第 \(i\) 个关键点的位置在 \(𝑝𝑜𝑠_𝑖\)。也就是说我们要枚举关键点,使得这一点的限速变为正无穷,重新计算“快速值”。但是 如果不加限制地直接枚举,最坏复杂度也会是 \(n^2\)。但我们发现在枚举当前关键点时,上一关键点和下一关键点仍然满足 \(𝑅𝑒𝑎𝑙_𝑖 == 𝑙𝑖𝑚𝑖𝑡_𝑖\) 的限制。也就是说我们在枚举第 \(i\) 个关键点时,只需要对 \([𝑝𝑜𝑠_𝑖−1, 𝑝𝑜𝑠_𝑖+1]\) 重新判断车速即可。这样均摊时间复杂度是 \(O(n)\) 的。

T4 距离产生美

对题意进行转化:枚举 limit,统计有多少选法的美丽值 >= limit,然后将所有方案求和就是答案。

举例:样例 1 中从 3 个数字选 2 个,三个数字分别为 [1,5,9]。

有三种选法:[1,5],[1,9],[5,9],美丽值求和为 4 + 8 + 4 = 16。

转化之后的题意:

统计有多少种选法的美丽值 >= 1:3 种
统计有多少种选法的美丽值 >= 2:3 种
统计有多少种选法的美丽值 >= 3:3 种
统计有多少种选法的美丽值 >= 4:3 种
统计有多少种选法的美丽值 >= 5:1 种
统计有多少种选法的美丽值 >= 6:1 种
统计有多少种选法的美丽值 >= 7:1 种
统计有多少种选法的美丽值 >= 8:1 种

将所有方案全部求和:3+3+3+3+1+1+1+1=16。

我们发现两种题意是等价的,现在考虑如何解决转化之后的题意。首先对数组进行排序,这样最小的差值一定是相邻的元素,然后我们就可以进行 dp 了。

\(dp[i][j]\) 表示看到位置 \(i\) 且选了第 \(i\) 个数字,总共选了 \(j\) 个数的方案数。我们枚举 \(limit(1 \leq limit \leq 32767)\),然后动态规划,内层动态规划需要三重循环(枚举 \(i,j\) 以及上一个转移过来的位置 \(k\),即从 \(dp[k][j-1]\) 转移到 \(dp[i][j]\))。时间复杂度为 \(O(N^2M*limit)\),可以通过大约 \(n \leq 50\) 的测试点。

接下来我们可以发现假设我们需要选择 \(M\) 个数字,那么美丽值最多为 \(limit / (M-1)\),所以我们无需从 1 枚举到 32767。此时的时间复杂度为 \(O(N^2*limit)\)

然后我们需要对动态规划进行优化,例如我们发现如果某一个位置 \(k\) 到当前位置 \(i\) 的差值大于等于 limit,那么所有比 \(k\) 更靠前的位置的数字和位置 \(i\) 的数字差值也大于等于 limit。满足二分性,可以通过二分 + 前缀和进行优化。

正解使用单调队列 + 前缀和进行优化,可以将时间复杂度优化到 \(O(N*limit)\)

posted @ 2025-04-13 18:42  sapo1o  阅读(15)  评论(0)    收藏  举报