描述
B国在耗资百亿元之后终于研究出了新式武器——连环阵(Zenith Protected Linked Hybrid Zone)。传说中,连环阵是一种永不停滞的自发性智能武器。但经过A国间谍的侦察发现,连环阵其实是由M个编号为1,2,…,M的独立武器组成的。最初,1号武器发挥着攻击作用,其他武器都处在无敌自卫状态。以后,一旦第i(1<=i< M)号武器被消灭,1秒种以后第i+1号武器就自动从无敌自卫状态变成攻击状态。当第M号武器被消灭以后,这个造价昂贵的连环阵就被摧毁了。
为了彻底打击B国科学家,A国军事部长打算用最廉价的武器——炸弹来消灭连环阵。经过长时间的精密探测,A国科学家们掌握了连环阵中M个武器的平面坐标,然后确定了n个炸弹的平面坐标并且安放了炸弹。每个炸弹持续爆炸时间为5分钟。在引爆时间内,每枚炸弹都可以在瞬间消灭离它平面距离不超过k的、处在攻击状态的B国武器。和连环阵类似,最初a1号炸弹持续引爆5分钟时间,然后a2号炸弹持续引爆5分钟时间,接着a3号炸弹引爆……以此类推,直到连环阵被摧毁。
显然,不同的序列a1、a2、a3…消灭连环阵的效果也不同。好的序列可以在仅使用较少炸弹的情况下就将连环阵摧毁;坏的序列可能在使用完所有炸弹后仍无法将连环阵摧毁。现在,请你决定一个最优序列a1、a2、a3…使得在第ax号炸弹引爆的时间内连环阵被摧毁。这里的x应当尽量小。
格式
输入格式
第一行包含三个整数:M、n和k(1<=M, n<=100,1<=k<=1000),分别表示B国连环阵由M个武器组成,A国有n个炸弹可以使用,炸弹攻击范围为k。以下M行,每行由一对整数xi,yi(0<=xi,yi<=10000)组成,表示第i(1<=i<=M)号武器的平面坐标。再接下来n行,每行由一对整数ui,vi(0<=ui,vi<=10000)组成,表示第i(1<=i<=n)号炸弹的平面坐标。输入数据保证随机、无误、并且必然有解。
输出格式
一行包含一个整数x,表示实际使用的炸弹数。
样例1
样例输入1[复制]
4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
0 0
样例输出1[复制]
2
限制
各个测试点2秒
来源
NOI2003 Day2 Problem3
【题解】
详细的题解参见IOI2004国家集训队论文 楼天城 《浅谈部分搜索+高效算法在搜索问题中的应用》
在百度文库里就能找到。
这里写一下缩略版的。
首先必然是一个炸弹要炸掉一个连续的武器区间,且炸弹要从第一个武器开始炸。
有可能一个连续的武器区间有多个炸弹能够都炸到。
于是转换成二分图匹配的模型
左边的点是[1..m]分割成的若干区间 右边的n个点则代表炸弹.
设can[i][j][k]表示k号炸弹能否从i号武器连续炸到j号武器
左边的点i和右边的点j有边的条件是can[li][ri][j]为true;
我们要用搜索来获取最少需要的炸弹数
每一层搜索枚举的是一个区间
这个区间要给它找一个匹配,即找一个炸弹来炸它。
当然你要保证这个区间能够被一些炸弹炸到。
因此你又得找一个maxl表示一个区间的上界(能够被炸到的上界);
然后枚举这中间的值。
有时候你还可以修改以前的匹配以获得更大的区间上界。
在搜索之前
用DP搞一个perfect[i]表示重复使用炸弹,i..m号武器最少需要多少个炸弹。
在搜索的时候可以用used+perfect[now] 和 已经获得的ans的关系来剪枝。
然后就是maxl的获取需要用到zd[i][j]表示j号炸弹从i号武器开始能够连续炸到的最大武器编号;
记住可以用求匹配的方法来更换之前的匹配以期获得更大的maxl;
注解写得比较详细点
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 110;
int m, n, k, zd[M][M], a[M], b[M], perfect[M], ans = 2100000000;
bool can[M][M][M],vis[M];
bool reach[M][M],g[M][M];
queue <int> dl;
struct abc
{
int x, y;
};
abc wuqi[M], zhadan[M];
void input(int &r)
{
r = 0;
char t = getchar();
while (!isdigit(t)) t = getchar();
while (isdigit(t)) r = r * 10 + t - '0', t = getchar();
}
int sqr(int x)
{
return x*x;
}
bool hungary(int x)//执行匈牙利算法给某个区间求一个炸弹的匹配。
{
for (int i = 1;i <= n;i++)
if (g[x][i] && !vis[i])
{
vis[i] = true;
if (b[i] == 0 || hungary(b[i]))
{
b[i] = x;
a[x] = i;
return true;
}
}
return false;
}
void sear_ch(int used, int now)
{
if (used + perfect[now] >= ans)//最优性剪枝
return;
if (now == m + 1)//如果已经把所有的武器都消灭了就记录答案退出
{
ans = used;
return;
}
int maxl = now - 1;//获取区间的上限
for (int i = 1; i <= 100; i++) vis[i] = false;
for (int i = 1;i <= n;i++)//如果之前没有用过这个炸弹来匹配
if (b[i] == 0)//就加入队列中
{
vis[i] = true;
dl.push(i);
}
while (!dl.empty())
{
int x = dl.front();
dl.pop();
if (maxl < zd[now][x])//如果用这个炸弹能够增大区间的上限就增加
maxl = zd[now][x];
for (int i = 1;i <= used;i++)//利用匈牙利算法的思路
if (g[i][x] && !vis[a[i]])//可以更改之前的匹配以期获得更大的区间上限
{//因为g[i][x]所以是可以用这个x来代替那个a[i]炸弹的。
vis[a[i]] = true;
dl.push(a[i]);
}
}
if (maxl == now - 1)//如果无法获得更大的区间上限则退出
return;
int tempa[M], tempb[M];
for (int i = 1; i <= 100; i++)//用于回溯
tempa[i] = a[i], tempb[i] = b[i];
for (int i = 1; i <= 100; i++) vis[i] = false;
used++;//枚举的区间又增加了
for (int i = 1; i <= 100; i++)//可以匹配的炸弹是那些能够从now炸到maxl的炸弹
g[used][i] = can[now][maxl][i];//当然这些匹配之后是可能发生变化的。(即可以修改)
hungary(used);//为这个区间匹配一个炸弹
for (int i = maxl; i >= now; i--)//一定要从区间的上限开始往下枚举
{//这样可以较快求出一个暂时的ans,以便我们用最优性剪枝
for (int j = 1; j <= 100; j++)//这个区间的可以匹配的点是很多的。
g[used][j] = can[now][i][j];//因为now..i <= now..maxl
sear_ch(used, i + 1);//所以can[now][i]里面为true的元素是比can[now][maxl]里为true的元素多的
//因此我们在上面那个宽搜里面可以用那些多出来的炸弹来修改原来的匹配;
}
for (int i = 1; i <= 100; i++)
a[i]=tempa[i], b[i] = tempb[i];
}
int main()
{
//freopen("F:\rush.txt", "r", stdin);
input(m); input(n); input(k);
for (int i = 1; i <= m; i++)
input(wuqi[i].x), input(wuqi[i].y);
for (int i = 1; i <= n; i++)
input(zhadan[i].x), input(zhadan[i].y);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)//reach[i][j]表示j号炸弹是否能炸到i号武器
reach[i][j] = (sqr(wuqi[i].x - zhadan[j].x) + sqr(wuqi[i].y - zhadan[j].y)) <= sqr(k);
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
can[i][i][j] = reach[i][j];//can[i][j][k]表示k号炸弹是否能连续从i号武器炸到j号武器
for (int j = 1; j <= n; j++)
for (int k = i + 1; k <= m; k++)
can[i][k][j] = can[i][k - 1][j] && reach[k][j];
for (int j = 1; j <= n; j++)
{
zd[i][j] = i-1;//zd[i][j]表示j号炸弹从i号武器开始能够连续炸到的最大武器编号
for (int k = i; k <= m; k++)
if (can[i][k][j])
zd[i][j] = k;
}
}
perfect[m + 1] = 0;//pefect[i]表示i..m这些武器炸掉,在可以重复使用炸弹时需要多少个炸弹。
for (int i = m; i >= 1; i--)
{
int t = i;
for (int j = 1; j <= n; j++)
if (zd[i][j] > t)
t = zd[i][j];
perfect[i] = perfect[t + 1] + 1;
}
sear_ch(0, 1);
printf("%d
", ans);
return 0;
}