37 条题解
-
5yxc (麒麟吃稀饭) LV 10 @ 2021-07-18 21:47:41
题面NOI2003
有 m 个靶子 (axj,ayj) 和 n 个箭塔 (bxi,byi)。每个箭塔可以射中距离在 k 以内的靶子。第 i+1 只有第 i 个靶子被射中时才能被射中。每个箭塔只能用一次,现在可以安排每个箭塔的射击顺序,求最少需要几个箭塔可以射光 m 靶子。
数据范围:1≤m,n≤100,1≤k≤1000,1≤axj,ayj,bxi,byi≤10000。蒟蒻语
爆搜神题,可惜题解都很晦涩,蒟蒻因为一个小错误折腾了一个晚上,现在拿到了最优解,于是准备写个逊逊的题解。*蒟蒻解
首先每个箭塔解决一个靶子区间。
所以可以爆搜每个区间和箭塔匹配,这很明显是个二分图匹配。
为了方便处理很多细节,设所有 i 为箭塔的下标,j 为靶子的下标。
设 bool coni,j 表示箭塔 i 与靶子 j 联通。
由于每个箭塔的每个负责区间只需用后缀就可以有解,所以记录 nexi,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* 做准备,还可以求出一个 mnj 表示打到靶子 j 的剩余步数下限。
L(j,0,m)R(i,0,n) con[i][j]&&(mn[j]=min(mn[j],mn[nex[i][j]]+1));
然后就可以开始惊心动魄的 DFS 了。
最直接的方法是先用 mnj 来剪枝 A 一下,然后用 nexi,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<<'\n'; return 0; }
-
12017-06-13 00:10:08@
参见ltc论文《浅谈部分搜索+高效算法在搜索问题中的应用》
本题使用搜索分段方案然后使用二分图匹配进行问题的解决。
尝试实现了一下ltc的程序(附带详细注释)
剪枝一:最优性剪枝+乐观估价
剪枝二:宽搜最大结束位置
剪枝三:边深搜边扩展交错路#include<algorithm> #include<iostream> #include<iomanip> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<cmath> #include<queue> using namespace std; inline const int Get_Int() { int num=0,bj=1; char x=getchar(); while(x<'0'||x>'9') { if(x=='-')bj=-1; x=getchar(); } while(x>='0'&&x<='9') { num=num*10+x-'0'; x=getchar(); } return num*bj; } struct Point { double x,y; } a[105],b[105]; double Dist(Point a,Point b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } int m,n,k,Reach[105][105],f[105][105][105],Max[105][105],score[105],ans=0x7fffffff/2; int map[105][105],vst[105],To[105],From[105]; bool Hungry_Dfs(int Now) { //寻找增广路 for(int i=1; i<=n; i++) if(map[Now][i]&&!vst[i]) { vst[i]=1; if(!To[i]||(Hungry_Dfs(To[i]))) { From[Now]=i; To[i]=Now; return true; } } return false; } void Dfs(int sum,int Now) { //已经用了sum个炸弹,炸到了Now(同时sum也表示炸掉了几段) if(Now>m) { ans=min(ans,sum); return; } if(sum+score[Now]>=ans)return; //最优性剪枝(已经用的+乐观估价>=已有答案) memset(vst,0,sizeof(vst)); queue<int>Q; int Maxl=Now-1; //下一个段最大结束位置 for(int i=1; i<=n; i++) { if(!To[i]) { //将所有没有匹配的炸弹入队 vst[i]=1; Q.push(i); } } while(!Q.empty()) { int Front=Q.front(); Q.pop(); Maxl=max(Maxl,Max[Front][Now]); for(int i=1; i<=sum; i++) if(map[i][Front]&&!vst[From[i]]) { //如果当前的炸弹可以被交换,那么当前的入队 vst[From[i]]=1; Q.push(From[i]); } } if(Maxl==Now-1)return; int From1[105],To1[105]; memcpy(From1,From,sizeof(From)); memcpy(To1,To,sizeof(To)); memset(vst,0,sizeof(vst)); for(int i=1; i<=n; i++)map[sum+1][i]=f[i][Now][Maxl]; //统计有的边 Hungry_Dfs(sum+1); //扩展交错路 for(int i=Maxl; i>=Now; i--) { //枚举结束位置 for(int j=1; j<=n; j++)map[sum+1][j]=f[j][Now][i]; //统计有的边 Dfs(sum+1,i+1); //继续搜索 } memcpy(From,From1,sizeof(From1)); memcpy(To,To1,sizeof(To1)); } int main() { m=Get_Int(); n=Get_Int(); k=Get_Int(); for(int i=1; i<=m; i++) { a[i].x=Get_Int(); a[i].y=Get_Int(); } for(int i=1; i<=n; i++) { b[i].x=Get_Int(); b[i].y=Get_Int(); } for(int i=1; i<=n; i++) //计算reach[i,j]:第i个炸弹能否炸到j武器 for(int j=1; j<=m; j++) Reach[i][j]=Dist(b[i],a[j])<=(k*k); for(int k=1; k<=n; k++) //计算f[i,j,k]:第i个炸弹能否炸到j~k这一段 for(int i=1; i<=m; i++) { f[k][i][i]=Reach[k][i]; Max[k][i]=i-1; if(f[k][i][i])Max[k][i]=i; for(int j=i+1; j<=m; j++) { f[k][i][j]=f[k][i][j-1]&&Reach[k][j]; if(f[k][i][j])Max[k][i]=j; //计算Max[i,j]:第i个炸弹从j开始炸所能达到的最大编号 } } for(int i=m; i>=1; i--) { //计算score[i]:如果可以重复使用炸弹,炸完i~m所需炸弹数(乐观估价) int j=i-1; for(int k=1; k<=n; k++)j=max(Max[k][i],j); score[i]=score[j+1]+1; } Dfs(0,1); printf("%d\n",ans); return 0; }
-
02022-02-27 15:55:25@
首页
题库
训练
讨论
比赛
作业
排名
real_hdsy_gyz
/ Vijos / 题库 / 智破连环阵 /
题解
36 条题解
0
real_hdsy_gyz LV 0
发表您的题解
4yxc (麒麟吃稀饭) LV 10 @ 7 个月前
题面NOI2003
有 m 个靶子 (axj,ayj) 和 n 个箭塔 (bxi,byi)。每个箭塔可以射中距离在 k 以内的靶子。第 i+1 只有第 i 个靶子被射中时才能被射中。每个箭塔只能用一次,现在可以安排每个箭塔的射击顺序,求最少需要几个箭塔可以射光 m 靶子。
数据范围:1≤m,n≤100,1≤k≤1000,1≤axj,ayj,bxi,byi≤10000。蒟蒻语
爆搜神题,可惜题解都很晦涩,蒟蒻因为一个小错误折腾了一个晚上,现在拿到了最优解,于是准备写个逊逊的题解。*蒟蒻解
首先每个箭塔解决一个靶子区间。
所以可以爆搜每个区间和箭塔匹配,这很明显是个二分图匹配。
为了方便处理很多细节,设所有 i 为箭塔的下标,j 为靶子的下标。
设 bool coni,j 表示箭塔 i 与靶子 j 联通。
由于每个箭塔的每个负责区间只需用后缀就可以有解,所以记录 nexi,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]));
Copy
为了后面 A* 做准备,还可以求出一个 mnj 表示打到靶子 j 的剩余步数下限。L(j,0,m)R(i,0,n) con[i][j]&&(mn[j]=min(mn[j],mn[nex[i][j]]+1));
Copy
然后就可以开始惊心动魄的 DFS 了。
最直接的方法是先用 mnj 来剪枝 A 一下,然后用 nexi,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<<'\n';
return 0;
}
Copy
超级牛逼卢本伟1号 @ 7 个月前
你这个沟弱,才七级!!!!!!麒麟吃稀饭 @ 7 个月前
@超级牛逼卢本伟1号: 你个脑瘫潘昕尧 @ 2 个月前
@麒麟吃稀饭: 你个脑残1
Bill_Yang LV 10 @ 4 年前
参见ltc论文《浅谈部分搜索+高效算法在搜索问题中的应用》
本题使用搜索分段方案然后使用二分图匹配进行问题的解决。
尝试实现了一下ltc的程序(附带详细注释)
剪枝一:最优性剪枝+乐观估价
剪枝二:宽搜最大结束位置
剪枝三:边深搜边扩展交错路#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(x<'0'||x>'9') {
if(x=='-')bj=-1;
x=getchar();
}
while(x>='0'&&x<='9') {
num=num*10+x-'0';
x=getchar();
}
return num*bj;
}
struct Point {
double x,y;
} a[105],b[105];
double Dist(Point a,Point b) {
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int m,n,k,Reach[105][105],f[105][105][105],Max[105][105],score[105],ans=0x7fffffff/2;
int map[105][105],vst[105],To[105],From[105];
bool Hungry_Dfs(int Now) { //寻找增广路
for(int i=1; i<=n; i++)
if(map[Now][i]&&!vst[i]) {
vst[i]=1;
if(!To[i]||(Hungry_Dfs(To[i]))) {
From[Now]=i;
To[i]=Now;
return true;
}
}
return false;
}
void Dfs(int sum,int Now) { //已经用了sum个炸弹,炸到了Now(同时sum也表示炸掉了几段)
if(Now>m) {
ans=min(ans,sum);
return;
}
if(sum+score[Now]>=ans)return; //最优性剪枝(已经用的+乐观估价>=已有答案)
memset(vst,0,sizeof(vst));
queue<int>Q;
int Maxl=Now-1; //下一个段最大结束位置
for(int i=1; i<=n; i++) {
if(!To[i]) { //将所有没有匹配的炸弹入队
vst[i]=1;
Q.push(i);
}
}
while(!Q.empty()) {
int Front=Q.front();
Q.pop();
Maxl=max(Maxl,Max[Front][Now]);
for(int i=1; i<=sum; i++)
if(map[i][Front]&&!vst[From[i]]) { //如果当前的炸弹可以被交换,那么当前的入队
vst[From[i]]=1;
Q.push(From[i]);
}
}
if(Maxl==Now-1)return;
int From1[105],To1[105];
memcpy(From1,From,sizeof(From));
memcpy(To1,To,sizeof(To));
memset(vst,0,sizeof(vst));
for(int i=1; i<=n; i++)map[sum+1][i]=f[i][Now][Maxl]; //统计有的边
Hungry_Dfs(sum+1); //扩展交错路
for(int i=Maxl; i>=Now; i--) { //枚举结束位置
for(int j=1; j<=n; j++)map[sum+1][j]=f[j][Now][i]; //统计有的边
Dfs(sum+1,i+1); //继续搜索
}
memcpy(From,From1,sizeof(From1));
memcpy(To,To1,sizeof(To1));
}
int main() {
m=Get_Int();
n=Get_Int();
k=Get_Int();
for(int i=1; i<=m; i++) {
a[i].x=Get_Int();
a[i].y=Get_Int();
}
for(int i=1; i<=n; i++) {
b[i].x=Get_Int();
b[i].y=Get_Int();
}
for(int i=1; i<=n; i++) //计算reach[i,j]:第i个炸弹能否炸到j武器
for(int j=1; j<=m; j++)
Reach[i][j]=Dist(b[i],a[j])<=(k*k);
for(int k=1; k<=n; k++) //计算f[i,j,k]:第i个炸弹能否炸到j~k这一段
for(int i=1; i<=m; i++) {
f[k][i][i]=Reach[k][i];
Max[k][i]=i-1;
if(f[k][i][i])Max[k][i]=i;
for(int j=i+1; j<=m; j++) {
f[k][i][j]=f[k][i][j-1]&&Reach[k][j];
if(f[k][i][j])Max[k][i]=j; //计算Max[i,j]:第i个炸弹从j开始炸所能达到的最大编号
}
}
for(int i=m; i>=1; i--) { //计算score[i]:如果可以重复使用炸弹,炸完i~m所需炸弹数(乐观估价)
int j=i-1;
for(int k=1; k<=n; k++)j=max(Max[k][i],j);
score[i]=score[j+1]+1;
}
Dfs(0,1);
printf("%d\n",ans);
return 0;
}Copy
-1SEX丶Elope LV 6 @ 9 年前
点击查看程序源码+详细题解-1
gj1111 LV 6 @ 12 年前
C=SQRT(A*A+B*B)IF C
-1
oimaster LV 10 @ 12 年前
看一下WC2004 LTC大牛的论文,立即就懂了,然后就是秒杀。顺便纪念第650次提交,311个AC……
-1
唯一.洋葱 LV 6 @ 13 年前
部分搜索+二分图匹配-1
ipip2005 LV 10 @ 13 年前
LTC毕竟是大牛一个,看论文、理解、分析、编程序,花了我10个小时左右-1
sanyun0606 LV 8 @ 13 年前
测试数据 01:答案正确... 0ms├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 04:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 05:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 06:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 07:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 08:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 09:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 10:运行时错误...| 错误号: 202 | 堆栈溢出错
为什么??????????
-1
SOS LV 4 @ 13 年前
很简单的-1
fengyi LV 9 @ 14 年前
做的头晕茫茫了。。。-1
xupeiqi LV 3 @ 14 年前
搜索+匹配编译通过...
├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms
-1
zzyynn LV 4 @ 14 年前
(2)每次匹配可以从以前的匹配开始扩展,不需要重新开始。如果当前的划分方法已经无法匹配成功,就没有搜索下去的必要了,只要每
搜索新的一段时立即通过匹配判断即可。
每次求匹配只要从原来的基础上扩展就可以了。
怎么匹配呢?什么扩展啊?
-1
jandy LV 8 @ 14 年前
样例输入 Sample Input4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
0 0
不是说共M+N+1行吗?样例怎么是M+N+2行?最后一行是干什么的?
-1
deadrain LV 3 @ 14 年前
第30个AC,虽然最后一个数据耗时比较多,还是纪念一下-1
SuperScz LV 3 @ 14 年前
编译通过...├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms
非Cheat.
膜拜LTC神牛中.
-1
DFDer LV 3 @ 14 年前
我CHEAT了,因为标准答案不是最优。我是调整搜索顺序+卡节点,后面6组都比标准答案优。最后一组我是74,标准答案是68-1
qq311755010 LV 3 @ 14 年前
楼天城的论文原文初步分析:
A国炸弹I可以炸到B国武器J的条件: (u[i]-x[j])2+(v[i]-y[j])2=Si,Ti+1=Si+1)。然后判断是否可以从A国的N颗炸弹中选出x
颗,分别可以炸掉其中的一段。
其实我们把搜索分为了两部分,先通过搜索将B国武器根据编号分为x段,再
通过搜索判断是否可以从A国的N颗炸弹中选出x颗,分别可以炸掉其中的一段。
其实第二部分可以用匹配来解决。
C[T] 表示A国炸弹I是否可以炸到B国武器S,S+1..T-1,T。
C=((u-x)2+(v-y)2
-1
Kyd LV 8 @ 15 年前
编译通过...├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms
-2
我是神 LV 10 @ 5 年前
#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\n", ans);
return 0;
}-2
Kenny10001 LV 7 @ 5 年前
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define l 110
int x1[l],y1[l],x2[l],y2[l],m,n,k,MAXT[l][l],ans=1000000000,a[l],b[l],vis[l],dl[l],head,tail,perfect[l];
bool reach[l][l],can[l][l][l],g[l][l];
void input_data()
{
scanf("%d%d%d",&m,&n,&k);
for (int i=1;i<=m;i++)
scanf("%d%d",&x1[i],&y1[i]);
for (int i=1;i<=n;i++)
scanf("%d%d",&x2[i],&y2[i]);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
reach[i][j]=((x1[i]-x2[j])(x1[i]-x2[j])+(y1[i]-y2[j])(y1[i]-y2[j]))<=(k*k);//reach[i][j]表示i号武器能否被j号炸弹炸到。
}
void pre()
{
for (int i=1;i<=m;i++)
{
for (int j=1;j<=n;j++)
can[i][i][j]=reach[i][j];
for (int j=i+1;j<=m;j++)
for (int d=1;d<=n;d++)
can[i][j][d]=can[i][j-1][d] && reach[j][d];//更新
for (int j=1;j<=n;j++)
{
MAXT[i][j]=i-1;//MAXT[i][j]表示j号炸弹从i号武器开始能够炸到的最大编号
for (int d=i;d<=m;d++)
if (can[i][d][j]) MAXT[i][j]=d;//利用can数组来更新MAXT数组
}
}
perfect[m+1]=0;//perfect[i]表示i-m号武器最少需要多少个炸弹才能炸掉(可以重复使用炸弹);
for (int i=m;i>=1;i--)
{
int t=i-1;
for (int j=1;j<=n;j++)
if (MAXT[i][j]>t) t=MAXT[i][j];//找到这个炸弹的最大使用限度。
perfect[i]=perfect[t+1]+1;//从t+1转移到当前状态。
}
}
bool xiongyali(int x)//匈牙利算法
{
for (int i=1;i<=n;i++)
if (g[x][i] && !vis[i])
{
vis[i]=true;
if (b[i]==0 || xiongyali(b[i]))
{
a[x]=i;
b[i]=x;
return true;
}
}
return false;
}
void search_ans(int used,int now)//used表示已经使用过的炸弹数,now表示now之前的武器都已经被炸毁
{
if (used+perfect[now]>=ans) return;//如果当前已经使用过的炸弹加上now---m的最少使用炸弹数>=ans则退出.
if (now==m+1)//如果已经炸掉了所有的武器则更新答案。
{
ans=used;
return;
}
int tempa[l],tempb[l],maxl=now-1;//开始划分这一段的区间。
memset(vis,false,sizeof(vis));
head=tail=0;
for (int i=1;i<=n;i++)
if (b[i]==0) //没有被使用过 则入队。
{
vis[i]=true;
dl[++tail]=i;
}
while (head<tail) { int k=dl[++head];//取出在队中的炸弹 if (maxl<MAXT[now][k])//如果这个炸弹从now开始能够炸到更大的编号则更新 maxl=MAXT[now][k]; for (int i=1;i<used;i++)//回过头来查看原先使用过的炸弹,看看能否用匈牙利算法的思路改变原来的匹配,获得更大的值。 if (g[i][k] && !vis[a[i]]) { vis[a[i]]=true; dl[++tail]=a[i]; } } if (maxl==now-1) return;//如果已经不能完成匹配了就结束。 used++; memcpy(g[used],can[now][maxl],sizeof(can[now][maxl]));//左边是区间,右边是炸弹,表示可以使用这些炸弹炸掉这段区间,因此连边。 memcpy(tempa,a,sizeof(a));//用于回溯 memcpy(tempb,b,sizeof(b)); memset(vis,false,sizeof(vis));//匈牙利算法的判重 xiongyali(used);//给used这个点匹配。 for (int i=maxl;i>=now;i--)//从大到小枚举区间。增强perfect数组的作用。
{
memcpy(g[used],can[now][i],sizeof(can[now][i]));
search_ans(used,i+1);
}
memcpy(a,tempa,sizeof(a));
memcpy(b,tempb,sizeof(b));
}
void output_ans()
{
printf("%d\n",ans);
}
int main()
{
input_data();
pre();
search_ans(0,1);
output_ans();
return 0;
}1 2 下一页 › 末页 »
智破连环阵
查看题目
递交
讨论
题解
信息
ID1018递交 Accepted难度7分类点击显示标签
NOI 2003
递交数1162我的递交数1已通过191通过率16%被复制10上传者 Vivian Snow
状态
评测队列
服务状态
开发
开源
API
支持
帮助
QQ 群
关于联系我们隐私服务条款版权申诉 Language
© 2005 - 2021 Vijos.orgbuild20210206-1-gb0e1685沪ICP备14040537号-1 -
-12013-02-16 10:12:39@
-
-12009-06-13 10:35:40@
C=SQRT(A*A+B*B)
IF C -
-12009-05-06 20:42:07@
看一下WC2004 LTC大牛的论文,立即就懂了,然后就是秒杀。
顺便纪念第650次提交,311个AC…… -
-12009-02-28 16:42:59@
部分搜索+二分图匹配
-
-12008-10-23 16:11:54@
LTC毕竟是大牛一个,看论文、理解、分析、编程序,花了我10个小时左右
-
-12008-10-22 18:12:59@
测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 04:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 05:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 06:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 07:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 08:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 09:运行时错误...| 错误号: 202 | 堆栈溢出错
├ 测试数据 10:运行时错误...| 错误号: 202 | 堆栈溢出错为什么??????????
-
-12008-10-02 23:28:11@
很简单的
-
-12007-12-11 22:28:20@
做的头晕茫茫了。。。
-
-12007-11-05 13:05:00@
搜索+匹配
编译通过...
├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms -
-12007-09-27 21:07:00@
(2)每次匹配可以从以前的匹配开始扩展,不需要重新开始。
如果当前的划分方法已经无法匹配成功,就没有搜索下去的必要了,只要每
搜索新的一段时立即通过匹配判断即可。
每次求匹配只要从原来的基础上扩展就可以了。怎么匹配呢?什么扩展啊?
-
-12007-07-31 09:47:06@
样例输入 Sample Input
4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
0 0不是说共M+N+1行吗?样例怎么是M+N+2行?最后一行是干什么的?
-
-12007-07-21 22:49:43@
第30个AC,虽然最后一个数据耗时比较多,还是纪念一下
-
-12007-05-15 20:45:21@
编译通过...
├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms非Cheat.
膜拜LTC神牛中. -
-12007-04-07 00:00:25@
我CHEAT了,因为标准答案不是最优。我是调整搜索顺序+卡节点,后面6组都比标准答案优。最后一组我是74,标准答案是68
-
-12007-03-27 17:40:09@
楼天城的论文原文
初步分析:
A国炸弹I可以炸到B国武器J的条件: (u[i]-x[j])2+(v[i]-y[j])2=Si,Ti+1=Si+1)。然后判断是否可以从A国的N颗炸弹中选出x
颗,分别可以炸掉其中的一段。
其实我们把搜索分为了两部分,先通过搜索将B国武器根据编号分为x段,再
通过搜索判断是否可以从A国的N颗炸弹中选出x颗,分别可以炸掉其中的一段。
其实第二部分可以用匹配来解决。
C[T] 表示A国炸弹I是否可以炸到B国武器S,S+1..T-1,T。
C=((u-x)2+(v-y)2 -
-12007-02-05 10:42:16@
编译通过...
├ 测试数据 01:答案正确... 0ms
├ 测试数据 02:答案正确... 0ms
├ 测试数据 03:答案正确... 0ms
├ 测试数据 04:答案正确... 0ms
├ 测试数据 05:答案正确... 0ms
├ 测试数据 06:答案正确... 0ms
├ 测试数据 07:答案正确... 0ms
├ 测试数据 08:答案正确... 0ms
├ 测试数据 09:答案正确... 0ms
├ 测试数据 10:答案正确... 0ms
---|---|---|---|---|---|---|---|-
Accepted 有效得分:100 有效耗时:0ms -
-22016-11-26 22:33:47@
#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\n", ans);
return 0;
}