P1526 [NOI2003]智破连环阵
又是被楼教主虐的体无完肤的一天
题意描述
在一个平面坐标系中,有 \(M\) 个目标点和 \(N\) 个炸弹,你的目标是用尽量少的炸弹炸毁所有目标。
目标点是有顺序的,只有每个目标点的前一个被毁灭,它才能被攻击。
每个炸弹可以炸到距离它不超过 \(k\) 个单位长度的点,一旦一个炸弹被启用,就可以一直对攻击范围内的点攻击。
求使用炸弹的最少数目。
康不懂走传送门。
算法分析
闲话
忙人自动略过。
为了写一道题康懂了一篇论文。(《匹配算法在搜索中的应用 楼天城》)
推荐大家都上网找找康康(找不到可以找我要),讲的肯定比蒟蒻我好啊。
初步分析
面对此类问题,一般只能使用搜索策略。
但是纯粹的搜索必然是超时的(不要问我为什么),所以可以结合二分图解决。
当然二分图不是一上来就可以想出来的,所以下面要一步一步引出思路。
具体思路
显然,一个目标点会且仅会被一个炸弹摧毁,所以可以建立二分图。
运用搜索策略将目标点分成 \(x\) 个集合,每个集合都由标号连续的一段目标点,并且可以被同一个炸弹摧毁。
将每个集合与能消灭它的炸弹连边。
进行二分图匹配。
每个集合都能匹配到炸弹时,\(x\) 即为一种合法解。
找到合法解中的最小值并输出。
自己可以试着实现以下。
但是打完之后发现自己还是 T 了,所以要剪枝,然后后面就是神仙思路了。
剪枝一
最优化剪枝。
可以预处理出炸掉目标 \(i...N\)(即 \(i\) 之后的目标点)的最少炸弹使用量 \(score[i]\)。
当搜索到目标点 \(i\) 时,如果“当前已经使用的炸弹数量 \(+ score[i] \geq\) 已有答案”,就剪枝。
这个剪枝还算好想的,具体实现见后。
剪枝二
可行性剪枝。
可以在搜索时计算出 \(MaxL\) 表示从当前点开始的集合的最大容量。
这样如果这个 \(MaxL=0\) 的话就没有往后搜的必要了(因为没有可行方案)。
具体实现之后再说。
剪枝三
减小搜索范围。
利用上面的 \(MaxL\),可以将搜索范围调整为 \(i+1...i+MaxL\),而不用 \(i...m\) 了。
这是个看似很简单但是很强力的剪枝。
总结一下
搜索+二分图匹配+三个剪枝=AC。
代码实现
代码实现较为冗长复杂,但是可以很好的锻炼代码能力。
预处理
首先声明几个变量:
\(f(i,s,t)\) 表示炸弹 \(i\) 能否炸毁 \(s..t\) 号目标点。
\(Max(i,s)\) 表示炸弹 \(i\) 从 \(s\) 开始炸能炸到的最远点,特殊的,如果 \(i\) 炸不到 \(s\),\(Max(i,s)=s-1\)。
然后就一步步预处理了:(注意注释)
double dis(node a,node b){return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
f[i][j][j]=(dis(b[i],a[j])<=(k*k));//先判断单点。
for(int k=1;k<=n;k++)
for(int i=1;i<=m;i++){
Max[k][i]=i-1;//赋初值。
if(f[k][i][i]) Max[k][i]=i;
for(int j=i+1;j<=m;j++){
f[k][i][j]=(f[k][i][j-1]&&f[k][j][j]);//简单的DP。
if(f[k][i][j]) Max[k][i]=j;//简单的判断。
}
}
剪枝一
\(score(i)\) 表示炸掉目标 \(i...N\)(即 \(i\) 之后的目标点)的最少炸弹使用量。
for(int i=m;i>=1;i--){
int j=i-1;
for(int k=1;k<=n;k++) j=max(Max[k][i],j);//用一颗炸弹能到达的最远点。
score[i]=score[j+1]+1;//需知定理:score(i)>=score(j)(i<j)
}
剪枝二
定义几个变量:
\(now\) 表示当前搜索的目标点,\(sum\) 表示当前搜索的炸弹。
\(match(i)\) 表示与炸弹 \(i\) 匹配的集合的开始节点。
\(from(i)\) 表示以这个节点开始的集合匹配的炸弹。(如果它并非开始节点而是中间节点,\(from(i)=0\))
\(map(i,j)\) 表示炸弹 \(i\) 与节点 \(j\) 之间是否有边。
memset(vis,false,sizeof(vis));//标记是否使用过
queue<int>q;
int Maxl=now-1;//赋初值。
for(int i=1;i<=n;i++)
if(!match[i])
vis[i]=true,q.push(i);//找到没有匹配的炸弹。
while(!q.empty()){
int x=q.front();q.pop();
Maxl=max(Maxl,Max[x][now]);//刷新答案。
for(int i=1;i<=now;i++){
if(map[i][x] && !vis[from[i]]){
//如果前面的节点(i)与当前炸弹(x)有边,且这个节点(i)原本匹配的炸弹(from(i))还没有访问过。
//这就意味着可以通过:将 i 与 x 匹配,now 与 from(i) 匹配来增加匹配的数量。
vis[from[i]]=true;//标记。
q.push(from[i]);//往下传递。
}
}
}
if(Maxl==now-1) return;//剪枝。(没有可行计划就不要往下走)
剪枝三
挺简单的吧...
for(int i=Maxl;i>=now;i--){//改变顺序,记得倒序搜索。
for(int j=1;j<=n;j++) map[sum+1][j]=f[j][now][i];//建边(倒序便是为了方便建边)
dfs(sum+1,i+1);//往下搜。
}
二分图匹配
看了半天你可能会问:二分图有什么用啊。
不着急,我们知道二分图的核心是寻找增广路,而这就可以增加匹配的数量。
而我们的目的就是每次增加匹配数量,所以在每次搜所时,过了剪枝二之后都要进行匹配。
因剪枝二已经证明其可以增广,所以不用再判断。
具体代码如下:
bool Dfs(int x){//其实不用 bool,但是写习惯了...。
for(int i=1;i<=n;i++)
if(map[x][i] && !vis[i]){
vis[i]=true;
if(!match[i] || Dfs(match[i])){
from[x]=i;
match[i]=x;
return true;
}
}
return false;
}
代码综合
将以上信息综合就是 AC 代码了。(之前有注释的地方将不再给出注释)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<queue>
#define N 110
using namespace std;
int m,n,k,f[N][N][N],Max[N][N],score[N],ans=0x3f3f3f3f;
int map[N][N],vis[N],match[N],from[N];
struct node{
double x,y;
}a[N],b[N];
int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
}
double dis(node a,node b){return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);}
bool Dfs(int x){
for(int i=1;i<=n;i++)
if(map[x][i] && !vis[i]){
vis[i]=true;
if(!match[i] || Dfs(match[i])){
from[x]=i;
match[i]=x;
return true;
}
}
return false;
}
void dfs(int sum,int now){
//结束条件。
if(now>m){
ans=min(ans,sum);
return;
}
//剪枝一。
if(sum+score[now]>=ans) return;
//剪枝二。
memset(vis,false,sizeof(vis));
queue<int>q;
int Maxl=now-1;
for(int i=1;i<=n;i++)
if(!match[i])
vis[i]=true,q.push(i);
while(!q.empty()){
int x=q.front();q.pop();
Maxl=max(Maxl,Max[x][now]);
for(int i=1;i<=sum;i++){
if(map[i][x] && !vis[from[i]]){
vis[from[i]]=true;
q.push(from[i]);
}
}
}
if(Maxl==now-1) return;
int from1[N],match1[N];
//为回溯做准备。
memcpy(from1,from,sizeof(from));
memcpy(match1,match,sizeof(match));
memset(vis,false,sizeof(vis));
//注意一下建图。
for(int i=1;i<=n;i++) map[sum+1][i]=f[i][now][Maxl];
Dfs(sum+1);
for(int i=Maxl;i>=now;i--){
//再建图。
for(int j=1;j<=n;j++) map[sum+1][j]=f[j][now][i];
dfs(sum+1,i+1);
}
//回溯。
memcpy(from,from1,sizeof(from1));
memcpy(match,match1,sizeof(match1));
return;
}
int main(){
//输入。
m=read(),n=read(),k=read();
for(int i=1;i<=m;i++)
a[i].x=read(),a[i].y=read();
for(int i=1;i<=n;i++)
b[i].x=read(),b[i].y=read();
//预处理。
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
f[i][j][j]=(dis(b[i],a[j])<=(k*k));
for(int k=1;k<=n;k++)
for(int i=1;i<=m;i++){
Max[k][i]=i-1;
if(f[k][i][i]) Max[k][i]=i;
for(int j=i+1;j<=m;j++){
f[k][i][j]=(f[k][i][j-1]&&f[k][j][j]);
if(f[k][i][j]) Max[k][i]=j;
}
}
for(int i=m;i>=1;i--){
int j=i-1;
for(int k=1;k<=n;k++) j=max(Max[k][i],j);
score[i]=score[j+1]+1;
}
//搜索。
dfs(0,1);
printf("%d\n",ans);
return 0;
}
结语
康后是否有些自闭...。
可以结合代码和论文多理解几遍。
完结撒花。
P1526 [NOI2003]智破连环阵的更多相关文章
- luogu P1526 [NOI2003]智破连环阵 搜索+最大匹配+剪枝
LINK:智破连环阵 考试的时候 题意没理解清楚 题目是指一个炸弹爆炸时间结束后再放另一个炸弹 而放完一个炸弹紧接另一个炸弹.题目中存在然后二字. 这样我们可以发现某个炸弹只会炸连续的一段. 但是 由 ...
- 题解-NOI2003 智破连环阵
题面 NOI2003 智破连环阵 有 \(m\) 个靶子 \((ax_j,ay_j)\) 和 \(n\) 个箭塔 \((bx_i,by_i)\).每个箭塔可以射中距离在 \(k\) 以内的靶子.第 \ ...
- bzoj4622 [NOI 2003] 智破连环阵
Description B国在耗资百亿元之后终于研究出了新式武器——连环阵(Zenith Protected Linked Hybrid Zone).传说中,连环阵是一种永不停滞的自发性智能武器.但经 ...
- 【21.00%】【vijos P1018】智破连环阵
描述 B国在耗资百亿元之后终于研究出了新式武器--连环阵(Zenith Protected Linked Hybrid Zone).传说中,连环阵是一种永不停滞的自发性智能武器.但经过A国间谍的侦察发 ...
- bzoj 4622: [NOI 2003] 智破连环阵【dfs+匈牙利算法】
一个炸弹炸一个区间的武器,想到二分图匹配 但是直接dfs断点显然不行,预处理出dis[i]为i到m的至多值来最优性剪枝,并且标记ok[i][j]为炸弹i可以炸到j武器,mx[i][j]为i炸弹从j武器 ...
- [luogu1526]智破连环阵
(以下在描述复杂度时,认为$n$和$m$相同,因此一律使用$n$) 称第$i$个炸弹能匹配非空区间$[l,r]$,当且仅当$l$到$r$内所有武器都在$i$攻击范围内,且$r=m$或第$r+1$个武器 ...
- ZJOI2017 Day1
私のZJOI Day1 2017-3-21 07:52:53 有人在暴力膜 苟-- 富贵 无相忘 ZJOI2017交流群 133135071 如果你足够厉害 如果你足够厉害 如果你足够厉害 其实完全可 ...
- 【NOI2003——搜索+二分图匹配优化】
A 文本编辑器 无旋treap真好看 B 木棒游戏 暴力神仙题 C 数据生成器 树的直径两端点为Y, Z D 智破连环阵 搜索+二分图匹配优化 第一次写欸 列一下 void dfs (int y,in ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
随机推荐
- CSS中居中的完全指南(中英对照翻译)
翻译自:https://css-tricks.com/centering-css-complete-guide/ Centering things in CSS is the poster child ...
- VSCode搭建golang环境
安装对应版本的Golang 略 VSCode安装对应 Go 插件 在应用商店安装即可:go VSCode安装 Go 工具: 在VSCode输入:Crtl + Shift + P 在弹出框输入:inst ...
- SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期
写在前面 通过前几篇文章的学习,我们从大体上了解了shiro关于认证和授权方面的应用.在接下来的文章当中,我将通过一个demo,带领大家搭建一个SpringBoot整合Shiro的一个项目开发脚手架, ...
- C#数据结构-双向链表
链表的概念以及链表与数组的差异不做过多的叙述,相信大家都耳熟能详,这里以c#语言实现简单的双向链表,作为备用,记录下~ public class Node<T> { private Nod ...
- MySQL数据库的完全备份与恢复
前言 在生产环境中,数据的安全性是至关重要的,任何数据的丢失都可能产生严重的后果,正确的数据库操作是实际环境下不可缺少的. 一.数据库备份的分类 1.1 从物理与逻辑的角度,备份的分类 物理备份:对数 ...
- 多测师讲解requests __介绍_高级讲师肖sir
我们今天讲解的内容 一.什么是Requests? Requests是用Python语言编写的简单易用的HTTP库,用来做接口测试的库. 二.安装requests库 1.按住Windows标志+r,在运 ...
- Linux Centos7 安装Docker-CE
先确保yum 是最新版本 执行: sudo yum update 添加docker源地址 sudo yum-config-manager --add-repo https://download.doc ...
- springMvc配置拦截器无效
说明 springMvc配置国际化拦截器失败,点击页面按钮切换中英文无效,排查发现没有进入 LocaleChangeInterceptor 类中,判断拦截器没有起作用,那么是什么原因导致拦截器无效,通 ...
- day30 Pyhton 复习模块
一.模块 什么是模块. 模块就是我们把装有特定功能的代码进行归类的结果. 从代码编写的单位来看我们的程序, 从小到大的顺序: 一条代码 < 语句句块 < 代码块(函数, 类) < 模 ...
- 用 C 语言游戏编程开发!果然最担心的事又发生了!
30了.我要怎么办,老了.人就像一头小毛驴,方向都是牵着的人定的. 这个项目从去年开始的,一个手机游戏,当时接这个项目的时候其实没有太多考虑,我一向都喜欢打肿脸充胖子的,好面子,人家找上门来,不能不给 ...