P2831 愤怒的小鸟
思路
一看到所有数范围这么小,就是一个赤裸裸的状态压缩DP了
设
d
p
[
S
]
dp[S]
dp[S]表示当前死了的猪集合为S时,至少用了多少只鸟
显然有以下方程
d
p
[
0
]
=
0
dp[0]=0
dp[0]=0
需要单独一只鸟打死这只猪时:
d
p
[
S
∣
(
1
<
<
(
i
−
1
)
)
]
=
m
i
n
(
d
p
[
S
∣
(
1
<
<
(
i
−
1
)
)
]
,
d
p
[
S
]
+
1
)
dp[S|(1<<(i-1))]=min(dp[S|(1<<(i-1))],dp[S]+1)
dp[S∣(1<<(i−1))]=min(dp[S∣(1<<(i−1))],dp[S]+1)
需要打死的猪在一条抛物线上时:
d
p
[
S
∣
l
i
n
e
[
i
]
[
j
]
]
=
m
i
n
(
d
p
[
i
∣
l
i
n
e
[
i
]
[
j
]
]
,
d
p
[
S
]
+
1
)
dp[S|line[i][j]]=min(dp[i|line[i][j]],dp[S]+1)
dp[S∣line[i][j]]=min(dp[i∣line[i][j]],dp[S]+1)
但是!这个算法的时间复杂度是
O
(
T
n
2
2
n
)
O(Tn^22^n)
O(Tn22n)
于是我们考虑优化:
令
x
x
x为满足
S
&
(
1
<
<
(
x
−
1
)
)
=
0
S\&(1<<(x-1))=0
S&(1<<(x−1))=0的最小正整数,即
x
x
x这个点当前不属于集合
S
S
S,则由
S
S
S扩展的所有线都要经过
x
x
x转移(因为这只猪必须要打),因为经过
x
x
x的所有线数量为
n
n
n,所以我们只要预处理处所有的
x
x
x即可降低一个
n
n
n的复杂度。
思路来源(感谢大佬)
取自dalao题解
细节:
- 在解方程时,考虑把 a , b a,b a,b当做未知量, x 2 , x , y x^2,x,y x2,x,y当做已知进行处理
- 注意精度
#include <bits/stdc++.h>
using namespace std;
const double eps=1e-8;
int t,dp[1<<20],line[20][20],n,m,lownbit[1<<20];
double x[20],y[20];
void epa(double &x,double &y,double a1,double b1,double c1,double a2,double b2,double c2){
y=(c2*a1-c1*a2)/(b2*a1-b1*a2);
x=(c1-b1*y)/a1;
}
int main(){
for(int i=0;i<(1<<18);i++){
int j=1;
for(;j<=18&&i&(1<<(j-1));j++);
lownbit[i]=j;
}
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
memset(line,0,sizeof(line));memset(dp,0x3f,sizeof(dp));dp[0]=0;
for(int i=1;i<=n;i++){
scanf("%lf%lf",&x[i],&y[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(fabs(x[i]-x[j])<eps) continue;
double a,b;
epa(a,b,x[i]*x[i],x[i],y[i],x[j]*x[j],x[j],y[j]);
if(a>-eps) continue;
for(int k=1;k<=n;k++){
if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<eps){
line[i][j]|=(1<<(k-1));
}
}
}
}
for(int i=0;i<(1<<n);i++){
int j=lownbit[i];
dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);
for(int k=1;k<=n;k++){
dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);
}
}
printf("%d\n",dp[(1<<n)-1]);
}
return 0;
}
总结:对于这道题,首先从数据范围入手猜测是状态压缩,然后考虑设计转移方程,对于题目条件中的抛物线,看看这只猪是和其他抛物线一起打还是单独进行转移,接着考虑如何降低时间复杂度,从需要被打掉的的猪的类型入手分析,考虑怎么去掉无用状态, b i n g o ! bingo! bingo!