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 ...
随机推荐
- safari 浏览器版本升级后提示“此网页出现问题,已重新载入网页” 解决办法
safari回退条件 版本回退的前提是关闭电脑的SIP机制,命令行 csrutil status 检测状态.Mac os 10.14以下版本回退Safari后插件还是可以用的,升了新系统退了也没法用了 ...
- C++逐字输出函数
void fun(string a) { for(int i=0;i<a.length();i++) { cout<<a[i]; usleep(10000); } cout<& ...
- 034 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 01 流程控制概述
034 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 01 流程控制概述 本文知识点:Java中的流程控制相关概念的认识 三大流程控制语句结构的简介 顺序 ...
- Java知识系统回顾整理01基础06数组02初始化数组
一.分配空间与赋值分步进行 分配空间与赋值分步进行 public class HelloWorld { public static void main(String[] args) { int[] a ...
- 机器学习算法——kNN(k-近邻算法)
算法概述 通过测量不同特征值之间的距离进行 [分类] 优点:精度高.对异常值不敏感.无数据输入假定. 缺点:计算复杂度高.空间复杂度高. 适用数据范围: 数值型 和 标称型 . 算法流程 数据 样本数 ...
- 网站搭建-云服务器ECS-镜像管理
学习笔记: 快照,系统盘可创建镜像,数据盘不可以. 实例可以直接创建镜像,包括系统盘和数据盘 复制镜像: 新购服务器,选择镜像(又买). 共享镜像: 账号ID就是UID 云市场获取镜像; 1. 创建新 ...
- Spring中的一些面试题
谈谈你对spring IOC和DI的理解,它们有什么区别? IoC [Inverse of Control] 控制反转的概念,就是将原本在程序中手动创建UserService对象的控制权,交由Spri ...
- 利用Python求解二元一次方程
本程序流程如下: (1)输入A.B.C (2)计算△ (3)判断解的个数 (4)计算解 (5)输出解 求:x2-3x+2=0的解 #输入A.B.C A=float(input("输入A:&q ...
- JVM系列【6】GC与调优1
JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 GC基础知识 什么是垃圾 没有任何引用指向的一个对象或多个对象(循环引 ...
- 苏州6617.9373(薇)xiaojie:苏州哪里有xiaomei
苏州哪里有小姐服务大保健[微信:6617.9373倩儿小妹[苏州叫小姐服务√o服务微信:6617.9373倩儿小妹[苏州叫小姐服务][十微信:6617.9373倩儿小妹][苏州叫小姐包夜服务][十微信 ...