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 ...
随机推荐
- React学习小记--setState的同步与异步
react中,state不能直接修改,而是需要使用setState()来对state进行修改,那什么时候是同步而什么时候是异步呢? 基础代码: setCounter = (v) => { thi ...
- list_for_eacy_entry图解
.
- 04 Storage and Calculation C语言中的存储和计算
文章内容来源于Programming Hub的学习记录,本人整理添加了中文翻译,如有侵权,联系本人删除 Variables C语言中的变量 Let's extend our mainfunction ...
- Java知识系统回顾整理01基础02面向对象01类和对象
一.面向对象实例--设计英雄这个类 LOL有很多英雄,比如盲僧,团战可以输,提莫必须死,盖伦,琴女 所有这些英雄,都有一些共同的状态 比如,他们都有名字,hp,护甲,移动速度等等 这样我们就可以设计一 ...
- Java知识系统回顾整理01基础05控制流程04 for
一.for 比较for和while public class HelloWorld { public static void main(String[] args) { //使用while打印0到4 ...
- 【漏洞复现】MSF添加ms17-010的exp脚本及攻击复现
原文地址:https://bbs.ichunqiu.com/thread-23115-1-1.html 本来今晚在准备复现最近的CVE-2017-11882,由于本人是小白一枚,不知道这么添加msf的 ...
- maven下载依赖包下载失败
在家办公,遇到项目的maven包下载不了,刚开始以为是vpn的问题,折腾半天反复确认之后没有发现什么问题. 同时试过阿里巴巴的maven仓库,删除过以来,重新导过包发现都不行. 后来在idea的设置里 ...
- Linux软件漏洞-1
RHSA-2018:3107-中危: wpa_supplicant 安全和BUG修复更新 漏洞编号:CVE-2018-14526 漏洞公告:wpa_supplicant中未经身份验证的EAPOL-Ke ...
- Windows7 组策略错误:“未能打开这台计算机上的组策略对象。您可能没有合适的权限。”
在 Windows 7 系统下,打开组策略时,出现 组策略错误 -- "未能打开这台计算机上的组策略对象.您可能没有合适的权限.".如下图所示: 解决方案: 1.进入"计 ...
- ASP课程实例1——简易的手机号抽奖
本程序用到了最基本的vbscript函数. 请大家注意它们的用法并熟悉asp网页的基本结构. inputbox,mid() ,replace(),rnd(),fix(),document.write ...