P2831 [NOIP2016 提高组] 愤怒的小鸟

题目突破要点:
1.发现最多有18头猪,(* ̄(oo) ̄),这么小的数据范围提示我们,这个题要么用搜索要么用状压;
2.如果搜索,那么每次枚举任意两头猪,找到一条抛物线,然后判断抛物线总共经过几头猪,找到最优抛物线,即最优方案
3.但是我们发现这样的做法明显时间复杂度超了,所以对于正解,我们考虑状压

正解:
我们用二进制表示当前位置上的猪是否被打掉,那么由于我们最后的dp转移与小猪的位置有关,所以我们预处理出每一种方案的第一个0的位置(也就是找到第一个打下的小猪是第几个),这样的方法可以降低我们的时间复杂度(看到后面就理解了)
之后我们枚举每一头猪,循环枚举它和剩下的猪构成的抛物线,如果a>0,那么一定没有解(物理规律)。之后再次枚举小猪,判断这个小猪是不是在当前抛物线上,如果是,那么我们设置这个小猪可以被打掉

for(int k=1; k<=n; k++){
  if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<e) lin[i][j]|=(1<<(k-1));//这一个小猪可以打掉 
  //lin[i][j]表示第ij个小猪决定的曲线打下猪的状态}

到这,我们的****预处理部分算是真正做好了,接下来就是dp了。总共有1<<n 种方案,我们判断分别每一种方案。先找到每种方案打下来的第1头小猪的编号,那么这头猪后面的猪要么是选择和这头猪在一条抛物线上,要么是新开一条抛物线打猪
之后我们处理每一只猪,对于第i种方案,表示我们已经有了这种方案,如果我们要打另一只小猪,那么我们的状态应该跟着转移,判断到底是用这条抛物线打猪好还是从i的状态上再打一头猪好。
全部转移完成后,输出 cout<<dp[(1<<n)-1]<<' ';

#include<bits/stdc++.h>
#define maxn 1<<19

using namespace std;

const double e=1e-8;
int t,n,m,dp[maxn],lin[19][19],start[maxn];
double x[maxn],y[maxn];
void find(double &a,double &b,int i,int j) {
	a=-(y[i]*x[j]-y[j]*x[i])/(x[j]*x[j]*x[i]-x[i]*x[i]*x[j]);
	b=(y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[i]*x[i]*x[j]);
}

int main() {
	for(int i=0; i<(1<<18); i++) {
		int j=1;
		for(; j<=18 && i&(1<<(j-1)); j++);
		start[i]=j;
	}
	/*对于每一个状态,我们从他的第一位开始穷举,
	i&(1<<(j-1))意思就是i这个状态的第j头pig是否被打掉。
	于是start[i]的意思就是i这个状态内第一个0的位置,也就是我们做dp的起始位置。
	*//*for(int i=0;i<(1<<18);i++)
	{
		int j=1;
		for(;j<=18 && i&(1<<(j-1));j++)
		{
			start[i]=j;
		}//找到第i个状态的第一只打掉的小猪在哪里
	}*/
	cin>>t;
	while(t--) {
		memset(lin,0,sizeof(lin));
		memset(dp,1,sizeof(dp));
		dp[0]=0;

		cin>>n>>m;
		for(int i=1; i<=n; i++) cin>>x[i]>>y[i];
		/*	for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++){
					if(fabs(x[i]-x[j])<e) continue;
					double a,b;
					find(a,b,i,j);
					if(a>-e) continue;
					for(int k=1;k<=n;k++)
						if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<e)
							lin[i][j]|=(1<<(k-1));
				}*/
		for(int i=1; i<=n; i++)
			for(int j=1; j<=n; j++) {
				if(fabs(x[i]-x[j])<e) continue;
				double a,b;
				find(a,b,i,j);
				if(a>-e) continue;//a>0
				for(int k=1; k<=n; k++) {
					if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<e) lin[i][j]|=(1<<(k-1));//这一个小猪可以打掉 
				}
			}

		for(int i=0; i<(1<<n); i++) {
			int j=start[i];
			dp[i|1<<(j-1)]=min(dp[i|1<<(j-1)],dp[i]+1);	//先预处理一下j
			//找到这个抛物线第一个打下来的猪的位置,打下这些猪的最小
			//花费就是打下这头猪时候的花费或者是先打下第i头猪再打第一头猪的花费+1
			for(int k=1; k<=n; k++) {
				dp[i|lin[j][k]]=min(dp[i|lin[j][k]],dp[i]+1);
			}


		}
		cout<<dp[(1<<n)-1]<<'
';
	}
	return 0;
}
原文地址:https://www.cnblogs.com/yxr001002/p/14513023.html