[CQOI2018] 解锁屏幕(状压dp)

problem

luogu-P4460

solution

题面以及数据告诉我们显然是状压 d p dp dp

f ( s , i ) : f(s,i): f(s,i): 经过的点集 s s s 最后一次画的点为 i i i 的方案数。

直接枚举下一个之前没被画的点 j j j 转移即可。

f ( s ∣ 2 j , j ) ← f ( s , i ) f(s|2^j,j)\leftarrow f(s,i) f(s2j,j)f(s,i)

但这里需要保证 i , j i,j i,j 两点间若存在点,必须这些点之前都被画过了。

我们预处理,开个 bitset \text{bitset} bitset g ( i , j ) : g(i,j): g(i,j): i , j i,j i,j 贡献且在 i , j i,j i,j 线段上的点集。

共线判断我们常用的是斜率,即 y k − y i x k − x i = y k − y j x k − x j \frac{y_k-y_i}{x_k-x_i}=\frac{y_k-y_j}{x_k-x_j} xkxiykyi=xkxjykyj

但计算机 / 0 /0 /0 是会 RE \text{RE} RE 的,所以我们尽量避免处罚,交叉相乘判断相等即可。

共线只是一个条件,必须是在 i , j i,j i,j 形成的线段上,所以和 i , j i,j i,j 的横纵坐标判断一下即可。

最后的最后,就是这道题可以不用完所有点

条件只说了画的点数不小于 4 4 4 即可。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 100000007
#define maxn 20
bitset < maxn > g[maxn][maxn];
int n, ans;
int X[maxn], Y[maxn], f[1 << maxn][maxn];

bool check( int l, int r, int i ) {
	if( (X[i] - X[l]) * (Y[i] - Y[r]) != (X[i] - X[r]) * (Y[i] - Y[l]) )
		return 0;
	if( (X[i] >= max(X[l], X[r]) or X[i] <= min(X[l], X[r])) and 
		(Y[i] >= max(Y[l], Y[r]) or Y[i] <= min(Y[l], Y[r])) ) 
			return 0;
	return 1;
}

signed main() {
	scanf( "%lld", &n );
	for( int i = 0;i < n;i ++ ) scanf( "%lld %lld", &X[i], &Y[i] );
	if( n < 4 ) return ! puts("0");
	for( int i = 0;i < n;i ++ )
	for( int j = 0;j < n;j ++ )
	for( int k = 0;k < n;k ++ )
		if( i == j or i == k or j == k ) continue;
		else if( check( i, j, k ) ) g[i][j][k] = 1;
	for( int i = 0;i < n;i ++ ) f[1 << i][i] = 1;
	for( int s = 0;s < (1 << n);s ++ ) {
		for( int i = 0;i < n;i ++ )
			if( f[s][i] )
				for( int j = 0;j < n;j ++ )
					if( s >> j & 1 ) continue;
					else {
						for( int k = g[i][j]._Find_first();k != g[i][j].size();k = g[i][j]._Find_next( k ) )
							if( ! (s >> k & 1) ) goto pass;
						(f[s | (1 << j)][j] += f[s][i]) %= mod;
						pass:;
					}
	}
	int ans = 0;
	for( int s = 0;s < (1 << n);s ++ )
		if( __builtin_popcount( s ) >= 4 )
			for( int i = 0;i < n;i ++ )
				(ans += f[s][i]) %= mod;
	printf( "%lld\n", ans );
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值