bzoj4622 [NOI 2003] 智破连环阵
Description
Input
Output
Sample Input
4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
Sample Input 2
10 10 45
41 67
34 0
69 24
78 58
62 64
5 45
81 27
61 91
95 42
27 36
91 4
2 53
92 82
21 16
18 95
47 26
71 38
69 12
67 99
35 94
Sample Output
2
Sample Output 2
5
HINT
输出数据为NOI原数据
输出数据由楼教主代码制作
原题有spj 此题去掉spj 只输出最优解
HINT
NOI2003 Day2 T3 感谢sxb_201上传
正解:搜索+$dp$+二分图最大匹配。
丧心病狂的$zjo$竟然把这题出成考试题。。我敢说这是我见过的最玄学的搜索题。。
首先,我们发现每个炸弹肯定炸一段连续的区间。那么有一个很直观的暴力的思路,那就是枚举区间。对于每个区间,能够完全覆盖的炸弹向它连边,跑一遍二分图最大匹配就行了。
这样显然是过不了的,所以我们要剪枝。我们假设每个炸弹可以重复使用,那么我们算出当前武器开始的所有武器最少还需几个炸弹才能消灭。
这个用$dp$来预处理。设$can[p][i][j]$表示$p$号炸弹,是否能够炸$[i,j]$这个区间。那么设$dis[i]$表示炸$[i,m]$需要的最少炸弹,易知$dis[i]=min(dis[j]+1)$,$i<j<=m+1$且$can[p][i][j-1]=1$,$dis[m+1]=0$。这个我们预处理就能得出了。
最优性剪枝:$now+dis[i]>=ans$则剪枝,$now$为当前炸弹数。
可行性剪枝:我们可以每次直接在原图的基础上进行增广,如果当前点不能进行增广,就不用再往下搜索了。
然而这些剪枝还是不足以通过全部数据,我们不妨从可行性剪枝上入手。
我们可以尝试求出当前区间右端点的最大值$maxl$,那么显然,$maxl$及其之前的端点都是可行的。
我们发现这样可以很大程度地优化时间。首先,我们可以从$maxl$到$l$依次枚举右端点,减少搜索量;其次,我们可以只进行一次增广,因为$can[p][i][j]>=can[p][i][j+1]$,那么右端点为$maxl$时连的边,在右端点减小时同样也会出现,并不会影响答案。
如何求出$maxl$?首先我们要求出辅助数组$maxt[p][l]$,表示炸弹$p$从$l$开始能炸到的最远的点的编号,这个很容易预处理出来,就不再赘述。
注意到匈牙利算法的过程,一个炸弹能够使用有两种情况。首先是这个炸弹没有出现在匹配边上;其次是这个炸弹虽然出现在匹配边上,但是它能够通过增广以后和当前区间匹配。那么我们就可以使用$bfs$来解决这个问题,求出所有能够使用的炸弹(具体操作看代码吧。。),然后不断地取$maxt[p][l]$的最大值,就能求出$maxl$了。
这就是本题的两个重要剪枝,加上这两个剪枝以后极限数据也可以瞬间求解了。
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <cstring> 5 #include <cstdlib> 6 #include <cstdio> 7 #include <vector> 8 #include <cmath> 9 #include <queue> 10 #include <stack> 11 #include <map> 12 #include <set> 13 #define inf (1<<30) 14 #define N (110) 15 #define il inline 16 #define RG register 17 #define ll long long 18 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) 19 20 using namespace std; 21 22 int can[N][N][N],maxt[N][N],g[N][N],dis[N],vis[N],lk[N],mt[N],x[N],y[N],u[N],v[N],q[N],n,m,k,cnt,ans; 23 24 il int gi(){ 25 RG int x=0,q=1; RG char ch=getchar(); 26 while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 27 if (ch=='-') q=-1,ch=getchar(); 28 while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); 29 return q*x; 30 } 31 32 il void pre(){ 33 for (RG int i=1;i<=m;++i) x[i]=gi(),y[i]=gi(); 34 for (RG int i=1;i<=n;++i){ 35 u[i]=gi(),v[i]=gi(); 36 for (RG int j=1;j<=m;++j) 37 g[i][j]=(u[i]-x[j])*(u[i]-x[j])+(v[i]-y[j])*(v[i]-y[j])<=k*k; 38 } 39 for (RG int p=1;p<=n;++p){ 40 for (RG int i=1;i<=m;++i) can[p][i][i]=g[p][i],maxt[p][i]=g[p][i]?i:i-1; 41 for (RG int i=1;i<m;++i) 42 for (RG int j=i+1;j<=m;++j){ 43 can[p][i][j]=can[p][i][j-1]&g[p][j]; 44 if (can[p][i][j]) maxt[p][i]=j; 45 } 46 } 47 for (RG int i=m;i;--i){ 48 dis[i]=inf; 49 for (RG int j=i;j<=m;++j) 50 for (RG int p=1;p<=n;++p) 51 if (can[p][i][j]) dis[i]=min(dis[i],dis[j+1]+1); 52 } 53 return; 54 } 55 56 il int hungry(RG int x){ 57 for (RG int v=1;v<=n;++v){ 58 if (!g[x][v] || vis[v]==cnt) continue; vis[v]=cnt; 59 if (!lk[v] || hungry(lk[v])){ mt[x]=v,lk[v]=x; return 1; } 60 } 61 return 0; 62 } 63 64 il void dfs(RG int l,RG int id){ 65 if (l>m){ ans=id-1; return; } if (id-1+dis[l]>=ans) return; 66 ++cnt; RG int LK[N],MT[N],h=0,t=0,maxl=l-1; 67 for (RG int i=1;i<=n;++i) if (!lk[i]) vis[i]=cnt,q[++t]=i; 68 while (h<t){ 69 RG int x=q[++h]; maxl=max(maxl,maxt[x][l]); 70 for (RG int i=1;i<id;++i) 71 if (g[i][x] && vis[mt[i]]!=cnt) vis[mt[i]]=cnt,q[++t]=mt[i]; 72 } 73 memcpy(LK,lk,sizeof(LK)),memcpy(MT,mt,sizeof(MT)); 74 for (RG int i=1;i<=n;++i) g[id][i]=can[i][l][maxl]; ++cnt,hungry(id); 75 for (RG int r=maxl;r>=l;--r){ 76 for (RG int i=1;i<=n;++i) g[id][i]=can[i][l][r]; dfs(r+1,id+1); 77 } 78 memcpy(lk,LK,sizeof(lk)),memcpy(mt,MT,sizeof(mt)); return; 79 } 80 81 il void work(){ 82 m=gi(),n=gi(),k=gi(),pre(),ans=n; 83 dfs(1,1),printf("%d\n",ans); return; 84 } 85 86 int main(){ 87 File("boom"); 88 work(); 89 return 0; 90 }