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 ...
随机推荐
- Python练习题 019:求分数序列之和
[Python练习题 019] 有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前20项之和. --------------------------------- ...
- 【Hadoop】伪分布式安装
创建hadoop用户 创建用户命令: sudo useradd -m hadoop -s /bin/bash 创建好后需要更改hadoop用户的密码,命令如下: sudo passwd hadoop ...
- This is Riv3r1and.
总是要弄个博客来搞的嘛.
- 069 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 01 综合案例-数组移位-案例需求
069 01 Android 零基础入门 01 Java基础语法 09 综合案例-数组移位 01 综合案例-数组移位-案例需求 本文知识点:综合案例-数组移位-案例需求 说明:因为时间紧张,本人写博客 ...
- linux 漏洞列表
#CVE #Description #Kernels CVE-2017-1000367 [Sudo](Sudo 1.8.6p7 - 1.8.20) CVE-2017-7494 [Samba Remot ...
- MFC 简介
参考:https://baike.baidu.com/item/MFC/2236974 MFC (微软基础类库) 编辑 锁定 讨论999 MFC(Microsoft Foundation Clas ...
- 【题解】hdu4757 【TJOI2018】异或
题目链接 题目大意:有一颗树,有点权,每次询问:一条路径\(x->y\)中与\(z\)异或的最大值,或是以\(x\)为根的子树中与\(y\)异或的最大值. 树剖--还是算了. 观察到,子树的\( ...
- 让我们创建屏幕- Android UI布局和控件
下载LifeCycleTest.zip - 278.9 KB 下载ViewAndLayoutLessons_-_Base.zip - 1.2 MB 下载ViewAndLayoutLessons_-_C ...
- fastjson,对象转json字符串的过程中对value为null的值的一些处理
前言 fastjson是一个非常好用的java库,用于操作对象json序列化等等. 问题 最近在写代码的时候遇到问题,通过JSON.toJSONString方法将一个实体对象转为json字符串,转出来 ...
- Springboot应用使用Docker部署
首先准备好springboot应用,然后打包,我这里已经准备好了一个jar包 然后上传到服务器,准备一个目录用于存放jar包和Dokerfile文件 编写Dokerfile文件 我这里写的很简单,就简 ...