【洛谷P4460】解锁屏幕【状压dp】

题目大意:

题目链接:https://www.luogu.org/problemnew/show/P4460
给出坐标系中nn个点的坐标,求有多少种连线方式使得没有连线经过在连这条线之前没有经过的点。


思路:

很容易发现,对于任意阶段,影响接下来连线的只有两个因素:已连线的点集和最后连到的点。
由于n20nleq 20,所以可以状压。
枚举点集、最后连到的点以及接下来要连的点。之后枚举所有没有选择的点,用叉积(或斜率)判断是否满足要求。这样我们就得到了一个暴力O(2n×n3)O(2^n imes n^3)的做法。
考虑能否压掉枚举没选择的点那一维。可以预处理出line[i][j]line[i][j]表示线段ijij中经过的点集(不包括iijj)。那么只要判断没选择的点集(~ss)和line[i][j]line[i][j]是否有交集就可以了。
时间复杂度O(2n×n2)O(2^n imes n^2)。跑得比较慢,但是stdstd的复杂度就是这样的了。


代码:

#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include <cstdio>
#include <iostream>
#define rr register
using namespace std;

const int N=30,MAXN=(1<<20)+10,MOD=100000007;
int n,ans,maxn,x[N],y[N],f[MAXN][N],line[N][N];

bool in(int k,int q,int p)
{
	if (x[k]<min(x[q],x[p])) return 0;
	if (x[k]>max(x[q],x[p])) return 0;
	if (y[k]<min(y[q],y[p])) return 0;
	if (y[k]>max(y[q],y[p])) return 0;
	return 1;
}

bool check1(int s,int k)
{
	return s&(1<<k-1);
}

bool check2(int q,int p,int k)
{
	if (in(k,q,p)&&(x[k]-x[q])*(y[p]-y[k])==(x[p]-x[k])*(y[k]-y[q])) return 0;
	return 1;
}

bool check3(int s)
{
	int cnt=0;
	for (;s;s>>=1)
		if (s&1) cnt++;
	return cnt>3;
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d%d",&x[i],&y[i]);
		f[1<<i-1][i]=1;
	}
	maxn=1<<n;
	for (rr int i=1;i<=n;i++)
		for (rr int j=1;j<=n;j++)
			for (rr int k=1;k<=n;k++)
				if (!check2(i,j,k)&&i!=j&&j!=k&&i!=k) line[i][j]|=(1<<k-1);
	for (rr int s=0;s<maxn;s++)
		for (rr int i=1;i<=n;i++)
			if (check1(s,i))
				for (rr int j=1;j<=n;j++)
					if (i!=j && !check1(s,j) && !(line[i][j]&(~s)))
						f[s|(1<<j-1)][j]=(f[s|(1<<j-1)][j]+f[s][i])%MOD;
	for (rr int s=0;s<maxn;s++)
		if (check3(s))
			for (rr int i=1;i<=n;i++)	
				if (check1(s,i)) ans=(ans+f[s][i])%MOD;
	printf("%d",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998164.html