题解 P2831 【愤怒的小鸟】

suncongbo

2017-09-14 21:36:24

Solution

,第一篇题解,有不妥之处请见谅 思路: # 解法:状压DP. g[x]表示二进制编码为x的状态所用的最小步数。 sol[i]表示第i组解。解共有s组。 二进制编码: 状压DP基本概念,二进制数每一位的1表示可以达到这个点,0表示不能达到这个点。 比如,g[10101001](二进制)表示达到第1,4,6,8个点(十进制)所用的最少抛物线数。 解: 能通过仅1条抛物线所达到的所有的点组成的二进制编码,叫做一个解。 本题主要分为以下几步: 预处理。此处可用递归。首先枚举两个点,通过两个点共抛物线构造二元一次方程,用公式将其解出。 公式:a1x+b1y=c1,a2x+b2y=c2,解得x = (b2\*c1-b1\*c2)/(a1\*b2-a2\*b1), y = (a1\*c2-a2\*c1)/(a1\*b2-a2\*b1). DP。此处可以理解为01背包。背包容量为(1<<n)-1,共s个物品,费用分别为sol[1],sol[2],...,sol[s],价值均为1,且要求把背包装满,求最小价值。 但是有一个极大的漏洞:比如我们要配出(以下均为二进制)10011000这个数,一种正确的配法是10000000+00011000,但是另一种拼法是10000000+00010111+00000001,发生了进位,虽然和依然是10011000,但是第1个点显然被打了2次。 改进方法:另加一重循环,判断是否有重复。时间复杂度O(n) 本步总时间复杂度:O(smn),其中m=1<<n,可以证明s<=n^2/2,故复杂度O(2^n\*n^3),期望得分75~85分。 优化 不难发现,比如一条抛物线恰好打中了1,4,6,8(均为十进制)这四个点,且不能打中其他的点。则这条抛物线使s的值增加了15(即(1),(4),(6),(8),(1,4),(1,6),(1,8),(4,6),(4,8),(6,8),(1,4,6),(1,4,8),(1,6,8),(4,6,8),(1,4,6,8).事实上,由于我们是用最少的抛物线打最多的点,因此最后一种(1,4,6,8)的方案比前14种的任意一种都更优。因此,只需在无法继续递归(即没有其他点可以打)时将方案添加到sol[s]中,很大程度上降低了s的大小。(但并不能改变最坏复杂度,因为有可能特造一数据使得无任意三点共抛物线) 最重要的优化:我们在背包中进行位运算判断是否重复,因此添加了重复的情况还要舍去,多一维复杂度。不妨逆向思考:g[j]=min(g[j],g[j-sol[i]+1),原来可以用要更新的点j的编号来表示用来更新此点的点j-sol[i],现在可以用用来更新此点的点的编号来表示此点。即用g[j]来更新其他点,而不是用其他点来更新g[j].这样,我们就可以写出被更新的点的编号:g[i|sol[j]]. 注意:二进制原来1+1=10,现在1|1=1,不会发生进位,因此无需特判,减小一维复杂度。 ## 总复杂度:O(s\*2^n),s<=n^2,即O(2^n\*n^2),期望得分100分。 ```cpp #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int MAXN = 18; const int MAXM = 262144; struct Point { double x,y; }; Point nd[MAXN+2]; int sol[MAXN*MAXN+MAXN]; int g[MAXM+2]; int n,m,c,s; void solve(double a1,double b1,double c1,double a2,double b2,double c2,double *ansx,double *ansy) { if(a1*b2 != a2*b1) { *ansx = (b2*c1-b1*c2)/(a1*b2-a2*b1); *ansy = (a1*c2-a2*c1)/(a1*b2-a2*b1); } } void DFS(int pos,int cod,double a,double b) { int i,j; double arga,argb; if(pos == n) return; if(cod == 0) { for(i=0; i<n; i++) { for(j=i+1; j<n; j++) { if(nd[i].x != nd[j].x) { solve(nd[i].x*nd[i].x,nd[i].x,nd[i].y,nd[j].x*nd[j].x,nd[j].x,nd[j].y,&arga,&argb); if(arga < -1e-6) { DFS(j,(1<<i)+(1<<j),arga,argb); } } } } } else { for(i=pos+1; i<n; i++) { if(fabs(nd[i].x*nd[i].x*a+nd[i].x*b-nd[i].y) < 1e-12) { DFS(i,cod+(1<<i),a,b); return; } } sol[s] = cod; g[cod] = 1; s++; } } bool match(int spos,int gpos) { int i; for(i=n-1; i>=0; i--) { if((sol[spos]&(1<<i)) && (gpos&(1<<i))) return false; } return true; } int main() { int t,i,j; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&c); m = 1<<n; for(i=0; i<n; i++) { scanf("%lf%lf",&nd[i].x,&nd[i].y); } if(n == 1) { printf("1\n"); continue; } memset(g,42,sizeof(g)); memset(sol,0,sizeof(sol)); s = 0; for(i=0; i<n; i++) { sol[s++] = 1<<i; g[1<<i] = 1; } DFS(0,0,0.0,0.0); g[0] = 0; for(j=0; j<s; j++) { for(i=0; i<m; i++) { if(g[i|sol[j]] > g[i]+1) g[i|sol[j]] = g[i]+1; } } printf("%d\n",g[m-1]); } return 0; } ```