P2831 愤怒的小鸟

斯德哥尔摩

2018-10-29 23:31:59

Personal

[P2831 愤怒的小鸟](https://www.luogu.org/problemnew/show/P2831) 看到$n\leq 18$,应该就想到状压$DP$了。 设$dp[s]$表示当前已经干掉的猪的压缩状态为$s$,所需要的最少的小鸟数。 然后可以得到方程: $$\left.\begin{array}{}dp[0]=0\\dp[s|line[i][j]]=\min\{dp[s]+1\}\\dp[s|(1<<(i-1))]=\min\{dp[s]+1\}\end{array}\right.$$ 复杂度$O(Tn^22^n)$。 乍一看,好像不会炸。。。 但是!当$n=18,T=5$时,就是$O(424673280)$!(别数了,$4$亿多。。。) 这不是铁定$TLE$嘛。。。 所以我们要想优化。 $No.1$: 我们发现当$i\in s,j\in s$时不用转移。 ~~这不是废话么。。。~~ 为什么呢? 如果这条线只经过至多三个点,因为其中一个点已被打到,所以可以通过另外两个点的状态转移。 如果$i,j$都被打到,则可以通过转移$3$(单独一个点)转移。 如果这条线经过多于三个点,则可以通过其它任选两个点转移。 优化了一波常数,但是核心复杂度并没有降下来。。。 $No.2$: 设$x$为满足$s\&(1<<(x-1))==0$的最小正整数,则由$S$扩展的转移的所有线都要经过$x$。 为什么? 我们想:先打$1,4$,再打$2,3$,和先打$2,3$,再打$1,4$,是不是一样的? 一样。 ~~那不就行了?!~~ 如果这一次转移不打$x$,那以后还要再回过头来打$x$。 于是这就是多余的转移。 因为经过$x$的线数量是$n$,所以每次转移涉及到的线就从$n^2$变成了$n$。 终于把核心复杂度降了下来。 所以只要预处理一下$[0,2^{18}]$的对应的$x$就可以了。 怎么预处理? 当然$O(n^3)$大暴力啊。。。 那,二次函数怎么办? ~~废话。。。~~ ~~初三的知识别跟我说不会。。。~~ 也就是求过$A(x_1,y_1),B(x_2,y_2)$两个点的二次函数$y=ax^2+bx$的方程。 那就暴力解方程嘛。。。 我们会解出来这个玩意: $$a=\frac{x_2y_1-x_1y_2}{x_1x_2(x_1-x_2)}$$ $$b=\frac{y_1}{x_1}-\frac{x_2y_1-x_1y_2}{x_2(x_1-x_2)}=\frac{y_1}{x_1}-ax_1$$ 然后带入即可。 到此,所有的问题都解决了。 总复杂度$O(Tn2^n)$了,这才是考场的正解。 附代码: ```cpp #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> #define MAXN 20 #define eps (1e-8) using namespace std; int n,S,special; int line[MAXN][MAXN],lowestbit[1<<MAXN],dp[1<<MAXN]; struct Pig{ double x,y; friend bool operator <(const Pig &p,const Pig &q){ if(p.x==q.x)return p.y<q.y; return p.x<q.x; } }a[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } void make(){//预处理 for(int i=0;i<(1<<18);i++){ int j; for(j=1;j<=18&&(i&(1<<(j-1)));j++); lowestbit[i]=j; } } void init(){ n=read();special=read(); S=(1<<n); for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].x,&a[i].y); sort(a+1,a+n+1); memset(dp,127,sizeof(dp)); memset(line,0,sizeof(line)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){//暴力判二次函数 if(a[i].x==a[j].x)continue; double A=(a[i].y*a[j].x-a[i].x*a[j].y)/(a[i].x*a[j].x*(a[i].x-a[j].x)); double B=a[i].y/a[i].x-a[i].x*A; if(A>-eps)continue; line[i][j]=(1<<(i-1))|(1<<(j-1)); for(int k=1;k<=n;k++){ if(i==k||j==k)continue; double y=a[k].x*a[k].x*A+a[k].x*B; if(fabs(y-a[k].y)<eps)line[i][j]|=(1<<(k-1)); } } } void work(){ dp[0]=0; for(int s=0;s<S;s++){ int i=lowestbit[s]; dp[s|(1<<(i-1))]=min(dp[s|(1<<(i-1))],dp[s]+1); for(int j=1;j<=n;j++)dp[s|line[i][j]]=min(dp[s|line[i][j]],dp[s]+1); } printf("%d\n",dp[S-1]); } int main(){ int t=read(); make(); while(t--){ init(); work(); } return 0; } ```