P1526 [NOI2003] 智破连环阵

# [NOI2003] 智破连环阵

## 题目描述

B 国在耗资百亿元之后终于研究出了新式武器——连环阵(Zenith Protected Linked Hybrid Zone)。传说中,连环阵是一种永不停滞的自发性智能武器。但经过 A 国间谍的侦察发现,连环阵其实是由 $M$ 个编号为 $1, 2,\ldots, M$ 的独立武器组成的。最初,$1$ 号武器发挥着攻击作用,其他武器都处在无敌自卫状态。以后,一旦第 $i$($1\leq i< M$)号武器被消灭,$1$ 秒种以后第 $i+1$ 号武器就自动从无敌自卫状态变成攻击状态。当第 $M$ 号武器被消灭以后,这个造价昂贵的连环阵就被摧毁了。

为了彻底打击 B 国科学家,A 国军事部长打算用最廉价的武器——炸弹来消灭连环阵。经过长时间的精密探测,A 国科学家们掌握了连环阵中 M 个武器的平面坐标,然后确定了 $n$ 个炸弹的平面坐标并且安放了炸弹。每个炸弹持续爆炸时间为 $5$ 分钟。在引爆时间内,每枚炸弹都可以在瞬间消灭离它平面距离不超过 $k$ 的、处在攻击状态的 B 国武器。和连环阵类似,最初 $a_1$ 号炸弹持续引爆 $5$ 分钟时间,然后 $a_2$ 号炸弹持续引爆 $5$ 分钟时间,接着 $a_3$ 号炸弹引爆$\ldots$以此类推,直到连环阵被摧毁。

显然,不同的序列 $a_1, a_2, a_3\ldots$ 消灭连环阵的效果也不同。好的序列可以在仅使用较少炸弹的情况下就将连环阵摧毁;坏的序列可能在使用完所有炸弹后仍无法将连环阵摧毁。现在,请你决定一个最优序列 $a_1, a_2, a_3\ldots$ 使得在第 $a_x$ 号炸弹引爆的时间内连环阵被摧毁。这里的 $x$ 应当尽量小。

## 输入格式

第一行包含三个整数:$M$、$n$ 和 $k$,分别表示 B 国连环阵由 M 个武器组成,A 国有 $n$ 个炸弹可以使用,炸弹攻击范围为 $k$。以下 $M$ 行,每行由一对整数 $x_i, y_i$ 组成,表示第 $i$ 号武器的平面坐标。再接下来 $n$ 行,每行由一对整数 $u_i, v_i$ 组成,表示第 $i$ 号炸弹的平面坐标。输入数据保证随机、无误、并且必然有解。

## 输出格式

一行包含一个整数 $x$,表示实际使用的炸弹数。

## 样例 #1

### 样例输入 #1

```
4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
```

### 样例输出 #1

```
2
```

## 提示

对于 $100\%$ 的数据,$1\leq M, n\leq 100$,$1\leq k\leq 1000$,$0\leq x_i, y_i\leq 10000$,$0\leq u_i, v_i\leq 10000$。

各个测试点 $2$ 秒。

#include <bits/stdc++.h>
using namespace std;
 
//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair((a),(b))
#define x first
#define y second
#define be(a) (a).begin()
#define en(a) (a).end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
#define R(i,a,b) for(int i=(a),I=(b);i<I;i++)
#define L(i,a,b) for(int i=(b)-1,I=(a)-1;i>I;i--)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;
 
/*
注意: i 是箭塔,j 是靶子,s 是区间
*/
 
//Data
const int N=1e2;
int m,n,k;
pair<int,int> a[N],b[N];
bitset<N> con[N];
#define f(x) ((x)*(x))
 
//Dfs
bitset<N> e[N],vis;
int nex[N][N+1],mn[N+1],mat[N],ans;
bool match(int s){ // 匈牙利匹配
	R(i,0,n)if(e[s][i]&&!vis[i]){
		vis[i]=true;
		if(!~mat[i]||match(mat[i]))	
			return mat[i]=s,true;
	}
	return false;
}
void dfs(int j,int s){
	if(ans<=s+mn[j]) return; //A*
	if(j==m) return void(ans=s);
	int cmat[N]; copy(mat,mat+n,cmat); // 这里的 cmat 你要是设为全局变量就死了,我在这里死了 2 个小时
	L(J,j+1,m+1){
		R(i,0,n) con[i][j]&&nex[i][j]>=J&&(e[s][i]=true);
		R(i,0,n) vis[i]=false; match(s)?dfs(J,s+1):void();
		R(i,0,n) con[i][j]&&nex[i][j]>=J&&(e[s][i]=false); //莫忘回溯
		copy(cmat,cmat+n,mat);
	}
}
 
//Main
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>m>>n>>k;
	R(j,0,m) cin>>a[j].x>>a[j].y;
	R(i,0,n) cin>>b[i].x>>b[i].y;
	R(i,0,n)R(j,0,m) con[i][j]=(f(a[j].x-b[i].x)+f(a[j].y-b[i].y)<=f(k));
	R(i,0,n) fill(nex[i],nex[i]+m+1,-1);
	R(i,0,n)L(j,0,m) con[i][j]&&(nex[i][j]=max(j+1,nex[i][j+1]));
	R(j,0,m) mn[j]=iinf;
	L(j,0,m)R(i,0,n) con[i][j]&&(mn[j]=min(mn[j],mn[nex[i][j]]+1));
	fill(mat,mat+n,-1),ans=min(n,m),dfs(0,0);
	// 夹杂点骚操作(正确性不保证,仅用来抢最优解:猜测最终 ans<=mn[0]+5),把 ans 的初始值和 mn[0]+5 取 min
	cout<<ans<<'\n';
	return 0;
}

P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内测人员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值