题解-NOI2003 智破连环阵

题面

NOI2003 智破连环阵

(m) 个靶子 ((ax_j,ay_j))(n) 个箭塔 ((bx_i,by_i))。每个箭塔可以射中距离在 (k) 以内的靶子。第 (i+1) 只有第 (i) 个靶子被射中时才能被射中。每个箭塔只能用一次,现在可以安排每个箭塔的射击顺序,求最少需要几个箭塔可以射光 (m) 靶子。

数据范围:(1le m,nle 100)(1le kle 1000)(1le ax_j,ay_j,bx_i,by_ile 10000)


蒟蒻语

爆搜神题,可惜题解都很晦涩,蒟蒻因为一个小错误折腾了一个晚上,现在拿到了最优解,于是准备写个逊逊的题解。


蒟蒻解

首先每个箭塔解决一个靶子区间。

所以可以爆搜每个区间和箭塔匹配,这很明显是个二分图匹配。

为了方便处理很多细节,设所有 (i) 为箭塔的下标,(j) 为靶子的下标。

bool (con_{i,j}) 表示箭塔 (i) 与靶子 (j) 联通。

由于每个箭塔的每个负责区间只需用后缀就可以有解,所以记录 (nex_{i,j}) 表示箭塔 (i) 在靶子 (j) 后面第一个射不到的靶子(即可用射到最右边的靶子下标 (+1))。

// 这是一个很显然的递推
R(i,0,n)L(j,0,m) con[i][j]&&(nex[i][j]=max(j+1,nex[i][j+1]));

为了后面 A* 做准备,还可以求出一个 (mn_j) 表示打到靶子 (j) 的剩余步数下限。

L(j,0,m)R(i,0,n) con[i][j]&&(mn[j]=min(mn[j],mn[nex[i][j]]+1));

然后就可以开始惊心动魄的 Dfs 了。

最直接的方法是先用 (mn_j) 来剪枝 A* 一下,然后用 (nex_{i,j}) 枚举下一个区间端点,用过的箭塔打个标记,匹配一个没用过的箭塔。

前文说过这是个二分图匹配,所以有个野蛮操作(二分图优化):每次区间找好后,直接匈牙利匹配看看能不能匹配得到箭塔。

这个操作时间复杂度比起原来操作是不增的。

但是这有什么用呢?要配上另一个骚操作:逆序枚举下一个区间开始端点。

由于用了匈牙利后完美匹配概率变高,所以就可以尽早找到优的答案,进一步 A* 剪枝。

然后就结束了,时限 (2s) 的题跑得最慢的点 (4ms),总时间 (31ms)

注意 Dfs 回溯算法两个坑:回溯不彻底、回溯用了全局变量。


代码

#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<<'
';
	return 0;
}

祝大家学习愉快!

原文地址:https://www.cnblogs.com/George1123/p/13741786.html