[Luogu] P4460 [CQOI2018]解锁屏幕

题目背景

使用过Android 手机的同学一定对手势解锁屏幕不陌生。Android 的解锁屏幕由3X3 个点组成,手指在屏幕上画一条线,将其中一些点连接起来,即可构成一个解锁图案。如下面三个例子所示:

题目描述

画线时还需要遵循一些规则:

  1. 连接的点数不能少于4 个。也就是说只连接两个点或者三个点会提示错误。

  2. 两个点之间的连线不能弯曲。

  3. 每个点只能“使用”一次,不可重复。这里的“使用”是指手指划过一个点,该点变绿。

  4. 两个点之间的连线不能“跨过”另一个点,除非那个点之前已经被“使用”过了。

对于最后一条规则,参见下图的解释。左边两幅图违反了该规则; 而右边两幅图(分别为2->4-1-3-6 和6->5-4->1->9-2) 则没有违反规则,因为在“跨过”点时,点已经被“使用”过了。

现在工程师希望改进解锁屏幕,增减点的数目,并移动点的位置,不再是一个九宫格形状,但保持上述画线的规则不变。请计算新的解锁屏幕上,一共有多少满足规则的画线方案。

题目解析

易证,Hsz是巨神。

状压dp,把每个点的状态记录下来,然后转移的时候注意处理4条规则。

复杂度很紧,注意常数

Code

我有个大胆的想法

#include<bits/stdc++.h>
using namespace std;const int mod = 100000007;
int n,ans,dp[21][1<<20],mid[21][21],X[21],Y[21];
inline bool judge(int a,int b,int c) {
    short xa=X[a],ya=Y[a],xb=X[b],yb=Y[b],xc=X[c],yc=Y[c];
    if((max(xa,xb)>=xc&&min(xa,xb)<=xc&&max(ya,yb)>=yc&&min(ya,yb)<=yc)) 
    return (float)(xa-xb)/(ya-yb)==(float)(xa-xc)/(ya-yc);return false;}
int main(register unsigned long long __locate,register unsigned long long __loc) {
    scanf("%d",&n);for(register int i=0;i<n;i++) {scanf("%d%d",&X[i],&Y[i]);dp[i+1][1<<i]=1;}
    for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(i^j) for(int k=0;k<n;k++) if((i^k)&&(j^k)) mid[i][j]|=judge(i,j,k)<<k;
    for(int i=1;i<(1<<n);i++) for(int j=0;j<n;j++) if((1<<j)&i) for(int k=0;k<n;k++) if(!((1<<k)&i))
    if((i&mid[j][k])==mid[j][k]) {dp[k+1][i|(1<<k)]+=dp[j+1][i];if(dp[k+1][i|(1<<k)]>=mod) dp[k+1][i|(1<<k)]-=mod;}
    for(int i=15;i<1<<n;i++) if(__builtin_popcount(i)>=4) for(int j=1;j<=n;j++) {if((1<<j-1)&i) ans+=dp[j][i];
    if(ans>=mod)ans-=mod;} printf("%d",ans);return 0;
}

 

转载于:https://www.cnblogs.com/floatiy/p/9848521.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值