P1526 [NOI2003]智破连环阵 [搜索+剪枝(二分图)]

2023-12-17 19:30

本文主要是介绍P1526 [NOI2003]智破连环阵 [搜索+剪枝(二分图)],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[ N O I 2003 ] 智 破 连 环 阵 [NOI2003]智破连环阵 [NOI2003]

在坐标轴第一象限中有 M M M 个武器, N N N 个炸弹, 每个武器按顺序开启 可被摧毁状态, 以下简称 B B B 状态, 若一个武器在炸弹的爆炸范围内, 则这个武器将被摧毁, 紧接着下一个武器将会开启 B B B 状态.

要求安排炸弹的爆炸顺序, 使得摧毁全部武器使用的炸弹最小 .

M , N ≤ 100 M, N \le 100 M,N100 .


最 初 想 法 \color{blue}{最初想法}

搜索? 直接退火就好了… 爆炸 .代码纪念 .


正 解 部 分 \color{red}{正解部分}

一个炸弹摧毁的一定是连续的一段区间, 所以整个武器序列可分为几段, 每段被都某个炸弹摧毁 .

设当前 D F S DFS DFS 到的区间右端点为 r r r, 划分了 c n t cnt cnt 个区间,

预处理出 M a x _ t [ i , j ] Max\_t[i,j] Max_t[i,j] 表示 i i i 炸弹从 j j j 武器开始炸起, 所能炸到的最右端点,
不难得出状态转移式 M a x _ t [ i , j ] = max ⁡ ( M a x _ t [ i , j + 1 ] , j ) Max\_t[i,j] = \max(Max\_t[i,j+1], j) Max_t[i,j]=max(Max_t[i,j+1],j) .

但是我们现在想要知道的是炸掉武器 [ i , M ] [i, M] [i,M] 需要炸弹的下界, 便于剪枝,
设为 M i n _ c o s t [ i ] Min\_cost[i] Min_cost[i], 不考虑炸弹个数的限制,
容易得出状态转移式 M i n _ c o s t [ i ] = min ⁡ ( M i n _ c o s t [ M a x _ t [ j , i ] + 1 ] + 1 ) Min\_cost[i]=\min(Min\_cost[Max\_t[j,i] + 1]+1) Min_cost[i]=min(Min_cost[Max_t[j,i]+1]+1) .

c n t + M i n _ c o s t [ r ] ≥ A n s cnt+Min\_cost[r] \geq Ans cnt+Min_cost[r]Ans, 则直接 r e t u r n return return, 作为一个 最优性剪枝 .


于是可以对武器序列的分界点进行搜索, 若一个炸弹可以炸掉一个区间, 则这个炸弹与那个区间连边, 最终得到类下图 ↓ ↓


观察可以发现这个图为标准的二分图,于是使用 匈牙利 D F S DFS DFS 决策时进行 可行性剪枝 .

a a a炸弹可以和 b b b区间连边需要满足的条件为

  • 可以完全覆盖这个区间, 即 M a x _ t [ a , l b ] > = r b Max\_t[a, l_b]>=r_b Max_t[a,lb]>=rb .

实 现 部 分 \color{red}{实现部分}

  • 匈牙利 可以在 D F S DFS DFS 过程中进行 .
  • 注意该倒序循环的不要忘记倒序循环 .
  • 注意全局数组会改变 .
  • 注意刚开始时需要赋值 A n s Ans Ans N N N, 不能赋值 i n f inf inf, 这样会导致答案得出前 最优性剪枝 失效 .
#include<bits/stdc++.h>
#define reg registerconst int maxn = 105;int M;
int N;
int K;
int Ans;
int mark[maxn];
int Min_cost[maxn];
int Max_t[maxn][maxn];bool Used[maxn];
bool like[maxn][maxn];struct Node{ int x, y; } A[maxn], B[maxn];bool Pd(Node zd, Node wq){int t1 = zd.x-wq.x, t2 = zd.y-wq.y;return t1*t1 + t2*t2 <= K*K;
}bool Find(int x){for(reg int i = 1; i <= N; i ++)if(like[i][x] && !Used[i]){Used[i] = 1;if(!mark[i] || Find(mark[i])){ mark[i] = x; return 1; }}return 0;
}void DFS(int k, int cnt){if(cnt + Min_cost[k] >= Ans) return ;if(k == M+1){ Ans = std::min(Ans, cnt); return ; }int Tmp_1[maxn];for(reg int i = k; i <= M; i ++){
//                memcpy(Tmp_1, mark, sizeof Tmp_1);for(reg int j = 1; j <= N; j ++){Tmp_1[j] = mark[j];if(Max_t[j][k] >= i) like[j][cnt+1] = 1; }memset(Used, 0, sizeof Used);if(Find(cnt+1)) DFS(i+1, cnt+1);for(reg int j = 1; j <= N; j ++){mark[j] = Tmp_1[j];if(Max_t[j][k] >= i) like[j][cnt+1] = 0;}
//                memcpy(mark, Tmp_1, sizeof mark);}
}int main(){scanf("%d%d%d", &M, &N, &K);for(reg int i = 1; i <= M; i ++) scanf("%d%d", &A[i].x, &A[i].y);for(reg int i = 1; i <= N; i ++) scanf("%d%d", &B[i].x, &B[i].y);for(reg int i = 1; i <= N; i ++)for(reg int j = M; j >= 1; j --)if(Pd(B[i], A[j])) Max_t[i][j] = std::max(Max_t[i][j+1], j);memset(Min_cost, 0x3f, sizeof Min_cost);Min_cost[M+1] = 0;for(reg int i = M; i >= 1; i --)for(reg int j = 1; j <= N; j ++)if(Pd(B[j], A[i]))Min_cost[i] = std::min(Min_cost[i], Min_cost[Max_t[j][i] + 1] + 1);Ans = N;DFS(1, 0);printf("%d\n", Ans);return 0;
}

这篇关于P1526 [NOI2003]智破连环阵 [搜索+剪枝(二分图)]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/505563

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

poj 3104 二分答案

题意: n件湿度为num的衣服,每秒钟自己可以蒸发掉1个湿度。 然而如果使用了暖炉,每秒可以烧掉k个湿度,但不计算蒸发了。 现在问这么多的衣服,怎么烧事件最短。 解析: 二分答案咯。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <c

poj 3258 二分最小值最大

题意: 有一些石头排成一条线,第一个和最后一个不能去掉。 其余的共可以去掉m块,要使去掉后石头间距的最小值最大。 解析: 二分石头,最小值最大。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <c

poj 2594 二分图最大独立集

题意: 求一张图的最大独立集,这题不同的地方在于,间接相邻的点也可以有一条边,所以用floyd来把间接相邻的边也连起来。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <sta

hdu 4517 floyd+记忆化搜索

题意: 有n(100)个景点,m(1000)条路,时间限制为t(300),起点s,终点e。 访问每个景点需要时间cost_i,每个景点的访问价值为value_i。 点与点之间行走需要花费的时间为g[ i ] [ j ] 。注意点间可能有多条边。 走到一个点时可以选择访问或者不访问,并且当前点的访问价值应该严格大于前一个访问的点。 现在求,从起点出发,到达终点,在时间限制内,能得到的最大