简要题意:
平面直角坐标系的第一象限有若干绿猪,小鸟要通过若干条函数解析线来消灭它们。每个小鸟可以把所有 \(y=ax^2 + bx (a<0)\) 上的 \((x,y)\) 的所有绿猪消灭,当然没有绿猪就是白走。问最少多少次后可以消灭所有的绿猪。\(T\) 组数据。
实际上,之前写过一篇 骗分导论,里面有点口出狂言,既然自己说了能 \(60pts\) 那还不来填坑?
对于 \(70 \%\) 的数据,\(n \leq 12\).
实际上,直接爆搜即可。
怎么搜?对于每个搜索状态,你可以用一个 \(\text{vector}\) 来记录当前 剩下绿猪的坐标,并记录当前步数。
由于 \(\text{vector}\) 可以方便的删除元素,我们枚举当前 \(\text{vector}\) 的两个坐标并计算出 \(a,b\) 的值,然后判断 \(a<0\),合法则将所有 \(y=ax^2+bx\) 上的绿猪删除,进入下一层。
这样的话,如果你没有开 \(\text{long double}\),可以得到 \(55pts\).
开了之后就是 \(70pts\). 脸黑啊
时间复杂度:\(O(\text{70pts})\).
期望得分:\(60pts\). 实际得分:\(70pts\).
暴力最强大!
对于 \(100 \%\) 的数据,\(n \leq 18\),\(T \leq 5\).
显然,我们会发现在上面的搜索中,冗余的状态太多了。
比方说你先把 \(1,4\) 一起打下来,再把 \(2,3\) 一起打下来,和把打它们的顺序换一下,本质有什么区别呢?——没有,但是搜索中会大量出现类似 重复搜索同一个状态 的现象,就会想斐波那契数列那样越滚越大,最终 \(\text{TLE}\).
就上面一点而言,假设造个数据,保证每两头绿猪都无法一起打下,即每次只能干掉一个,那这个程序的时间复杂度 \(\cdots \cdots\)
所以我们想解决这个问题,但是你认为如果用 \(\texttt{map<vector<pair<db,db> > >}\) 实在是不好,还多一个 \(\log\),就和正解阴差阳错了。所以我们想到,记忆化搜索的本质不就是 \(\text{dp}\)?而枚举状态的 \(\text{dp}\) 不就是 \(\cdots \cdots\)
状态压缩 \(\text{dp}\),简称状压!
我们可以用一个二进制数来表示当前状态(显然只有打下和没打下两个情况),然后先把所有解析线(至少能打下 \(2\) 头绿猪的)统计在一起,然后枚举状态转移即可。但这个说法不太清晰,需要建立理论模型。
用 \(f\) 存储状态,\(f_0\) ~ \(f_{{2^n}-1}\) 即为所有状态。把当前解析线能消灭的猪也有一个二进制数存在 \(s\) 里,每次枚举状态的时候,对于每个 \(s\) 都要考虑一个转移:
f[k|s[i]]=min(f[k|s[i]],f[k]+1);
即如果当前线包含 \(i\) 则直接用线解决;否则 \(+1\).
但是你发现会有一些绿猪,只能单独被打,不能和别的猪一起被打。
对于这样的猪,我们需要再枚举所有的猪,将它更新一遍。
f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);
显然对于第 \(k\) 个状态,当前所有猪被打的答案可以直接更新掉,解决了这个问题。
最后\(f_{{2^n}-1}\) 就是答案。
时间复杂度:\(O(2^n \cdot n^2)\).(不过 \(\text{AThousandSuns}\) 提出了一种 \(O(2^n \cdot n)\) 的算法,详见 AThousandSuns 的洛谷博客)
期望得分:\(85pts\). 实际得分:\(100pts\).
理论上跑满了约为 \(8.4 \times 10^7\),挺危险的,擦边球过去了。
这里你就会明白为什么记忆化搜索过不了,因为,记忆化用 \(\text{map}\) 的时间复杂度是 \(O(2^n \cdot n^2 \log n)\),你就多了 \(4\) 倍的常数,成功到了 \(2.7 \times 10^8\) 一个更危险的级别。当然常数好卡过去我也不说什么了
注意一下:计算解析式的时候可能存在一定精度问题,因此保留 \(10^{-6}\) 即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define db long double
const int N=1e6+1;
inline int read(){char ch=getchar(); int f=1; while(ch<‘0‘ || ch>‘9‘) {if(ch==‘-‘) f=-f; ch=getchar();}
int x=0; while(ch>=‘0‘ && ch<=‘9‘) x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar(); return x*f;}
inline void write(int x) {
if(x<0) {putchar(‘-‘);write(-x);return;}
if(x<10) {putchar(char(x%10+‘0‘));return;}
write(x/10);putchar(char(x%10+‘0‘));
}
int n,m,T,cnt=0;
int f[N],s[501];
db x[101],y[101];
int main() {
T=read(); while(T--) {
n=read(),m=read();
memset(f,0x3f,sizeof(f));
memset(s,0,sizeof(s));
cnt=0; f[0]=0; //初始化
for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(x[i]-x[j]) {
double a=(y[i]-y[j]*x[i]/x[j])/x[i]/(x[i]-x[j]);
double b=(y[i]*x[j]*x[j]/x[i]/x[i]-y[j])/(x[j]*x[j]/x[i]-x[j]);
if(a<0) { //计算合法的解析式
cnt++;
for(int k=1;k<=n;k++)
if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=1e-6) s[cnt]|=(1<<(k-1));
} //记录解析式
}
for(int k=0;k<=(1<<n)-1;k++) {
for(int i=1;i<=cnt;i++) f[k|s[i]]=min(f[k|s[i]],f[k]+1);
for(int i=1;i<=n;i++) f[k|(1<<(i-1))]=min(f[k|(1<<(i-1))],f[k]+1);
} printf("%d\n",f[(1<<n)-1]);
}
return 0;
}
原文:https://www.cnblogs.com/bifanwen/p/13089466.html