洛谷 P9709 [KMOI R1] 军事行动
题目背景
题目描述
喵星边境局势愈发紧张,导致发生边境冲突。喵星军队总司令小袁立即对 \(Y\) 星采取军事行动。
整个宇宙可以看作一个平面直角坐标系,城市 \(1,2,\dots,n\) 的坐标分别为 \((x_1,y_1),(x_2,y_2),\dots(x_n,y_n)\)。
现在小袁率领的若干支舰队(可以理解为小袁的军事力量是无限的)驻扎在边境要塞,城市 \(1\) 处。他的舰队会进行以下移动:
- 如果舰队的坐标为 \((x,y)\),那么一天之后它可以移动到 \((x-1,y+2)\) 或 \((x+1,y+2)\) 或 \((x+2,y+1)\) 或 \((x-2,y+1)\) 或 \((x-1,y-2)\) 或 \((x+1,y-2)\) 或 \((x+2,y-1)\) 或 \((x-2,y-1)\) 处。
其中环绕在外的还有一条小行星带,当舰队的坐标 \((x,y)\) 且 \(x\le 0\) 或 \(y\le 0\) 或 \(m < x\) 或 \(m < y\) 时,舰队就会撞到小行星带。这是小袁所不想看到的。
现在小袁要攻打城市 \(2,3,\dots,n\),每一次他都会从一个已经占领的城市(城市 \(1\) 也算),派出舰队前往城市 \(i\) 并攻打它,舰队到达之后的第二天城市 \(i\) 就被攻占了。
特别的,小袁在一个舰队前往攻打或攻打一个城市的时候不会派出另外一支舰队,在攻占一座城市后当天可以立即派出另外一支舰队。
小袁想问,最少要花多少时间才能攻占所有的城市。
攻打顺序可以不按照 \(2,3\dots n\) 的顺序。
输入格式
第一行一个整数 \(n,m\),表示城市个数和小行星带的范围。
接下来 \(n\) 行,每一行两个正整数 \((x_i,y_i)\),表示城市 \(i\) 的坐标。保证 \(1\le x_i,y_i \le m\)。
输出格式
一个整数 \(ans\),表示最少要花的时间。
输入输出样例 #1
输入 #1
2 20
1 1
1 3
输出 #1
3
输入输出样例 #2
输入 #2
3 150
1 2
2 4
4 3
输出 #2
4
输入输出样例 #3
输入 #3
10 10
1 4
2 3
2 6
3 6
10 3
1 5
4 2
5 3
2 8
9 2
输出 #3
23
输入输出样例 #4
输入 #4
查看附件的 example4.in
输出 #4
查看附件的 example4.out
说明/提示
样例一解释:
舰队在第一天来到了 \((3,2)\) 的位置,第二天到达了城市 \(2\) 的位置,第三天占领了城市 \(2\)。总共花了 \(3\) 天。
样例二解释:
舰队在第一天到达了城市 \(2\) 的位置,第二天占领了城市 \(2\)。第三天到达了城市 \(3\) 的位置,第四天占领了城市 \(3\)。总共花了 \(4\) 天。
数据范围
本题采用 Subtask 捆绑测试。
| 子任务编号 | 测试点编号 | \(n\) | \(m\) | 特殊性质 | 分值 |
|---|---|---|---|---|---|
| \(1\) | \(1\sim2\) | \(1\le n\le 7\) | \(4\le m\le 7\) | 无 | \(10\) |
| \(2\) | \(3\sim7\) | \(1\le n\le 200\) | \(4\le m\le 70\) | 无 | \(25\) |
| \(3\) | \(8\sim9\) | \(1\le n\le 150\) | \(4\le m\le 150\) | 有 | \(15\) |
| \(4\) | \(10\sim20\) | \(1\le n\le 2000\) | \(4\le m\le 150\) | 无 | \(50\) |
特殊性质:对于每一个 \(1\le i\le n-1\),都有 \(x_i = x_{i+1}\)。
数据严格保证不会有不同的城市拥有相同的坐标。
算法分析
看到\(n和m\),都不大,想到可以暴力,思路很简单,先把输入处理一下,然后跑\(n\)边\(bfs\),求出每一个点到其他点的最短路,建边,然后对这个完全图跑一边\(kruskal\),求出权值和,输出即可,总体不难。
坑点
- 一共有8个方向,方向数组不要写错了。
- 题目说了,不能越界。
- “舰队到达之后的第二天城市 \(i\) 就被攻占了”,这句话说明要在每条边上再+1。
- 攻打的顺序不重要。
- 由于要跑\(n\)边\(bfs\),所以每跑完一次数组要清空。
AC代码
#include<bits/stdc++.h>
using namespace std;
int dx[] = {-1,1,2,-2,-1,1,2,-2};
int dy[] = {2,2,1,1,-2,-2,-1,-1};//方向数组
struct node{
int x,y,z;//结构体
};
bool cmp(node a,node b){//排序规则
return a.z<b.z;
}
int n,m;
int k = 0;
node a[2005];
int fa[2005];
queue<node> q;//bfs队列
node b[4000005];
bool vis[155][155];//标记
int vis2[155][155];//为了防止有重边,要用int存
long long sum = 0;
void bfs(int st){
int cnt = 0;
vis[a[st].x][a[st].y] = 1;//标记起点
q.push(a[st]);
while(!q.empty()){
node pos = q.front();
q.pop();
for(int i = 0; i<8; i++){
int xx = dx[i]+pos.x;
int yy = dy[i]+pos.y;
if(xx>0 && xx<=m && yy>0 && yy<=m && vis[xx][yy]==0){//满足条件
if(vis2[xx][yy]>st){
cnt++;
b[++k] = node{st,vis2[xx][yy],pos.z+2};//加入边中
if(cnt==n-st){//建完边了
while(!q.empty()){//清空队列
q.pop();
}
return ;
}
}
vis[xx][yy] = 1;
q.push(node{xx,yy,pos.z+1});
}
}
}
}
int find(int x){//并查集
if(fa[x]==x){
return x;
}else{
return fa[x] = find(fa[x]);
}
}
void kruskal(){
int cnt = 0;
sort(b+1,b+1+k,cmp);//排序
for(int i = 1; i<=k; i++){
int x = find(b[i].x);
int y = find(b[i].y);
if(x!=y){
fa[x] = y;
sum+=b[i].z;//加上边权
cnt++;
if(cnt==n-1){//已经是生成树了
return ;
}
}
}
}
int main(){
cin>>n>>m;
for(int i = 1; i<=n; i++){
fa[i] = i;//初始化
cin>>a[i].x>>a[i].y;
a[i].z = 0;//置零,方便存储
vis2[a[i].x][a[i].y] = i;//标记
}
for(int i = 1; i<n; i++){
bfs(i);
memset(vis,0,sizeof(vis));//归零
}
kruskal();//最小生成树
cout<<sum;//输出边权
return 0;
}
总结
这题只要注意到\(n和m\)的范围,就很容易想到暴力,暴力的代码很好写,就是稍加改动的\(bfs\)和\(kruskal\)的模板,时间复杂度是\(O(nm^2)\),大约为45000000,不会爆,总体来说这题算比较简单的绿题了。

浙公网安备 33010602011771号