密码

Description

升降梯的密码盘是一个由n*n个方格组成的正方形(n为偶数),第i行第j列的方格中标着数字(i-1)*n+j,而在密码盘的上面覆盖着一张同样由n*n个方格组成的挡光片。如果挖去挡光片上的n*n/4个格子,并从小到大记下通过挖去的格子看到的n*n/4个数;然后把挡光片顺时针旋转90°、180°、270°,每次也同样记下看到的n*n/4个数;这样最终将得到n*n个数。如果记下的这n*n个数恰好是1~n*n的一个排列,那么我们称这片挖去n*n/4个格子后的挡光片对密码盘是“精确覆盖”的。不妨用一个n*n的01矩阵表示这张挡光片,其中挖去的格子为1,没有挖去的格子为0,那么如下图所示的挡光片就可以表示为: 
 
0000 
0000 
1101 
0001 
定义挡光片A比挡光片B小,当且仅当A对应的01矩阵的字典序小于B对应的01矩阵的字典序(即:A和B对应的01矩阵中存在一个位置(x,y),使得矩阵A、B中前x-1行的所有数字和第x行的前y-1个数字都相同,而A中第x行第y列的数字为0,B中第x行第y列的数字为1)。 
现在升降梯口的墙上写着一个数字k,探险队员们必须迅速制作出第k小的、对密码盘“精确覆盖”的挡光片,用以在密码盘上获取n*n个数作为开启升降梯动力的密码。

Input

一行两个正整数n、k。

Output

输出满足要求的挡光片对应的01矩阵。

Sample Input

4 15

Sample Output

0000
0000
1101
0001

Hint

测试点编号    n        k 
      #1          =2      <=10 
      #2          =2      <=10^3 
      #3          =4      <=10 
      #4          =4      <=10^9 
      #5          =6      <=10 
      #6          =6      <=10^18 
      #7          =8      <=10 
      #8          =8      <=10^18 
      #9          =10    <=10^18 
      #10        =10    <=10^18 
数据保证有解。


【分析】

        这道题思考起来真的很有难度。首先我们不难判断出可以将整个N×N的正方形分成四块,然后其中一块可以通过旋转得到整个图形。不妨以样例为例,若用ABCD对遮光片进行编号,我们可以得到这样一个图。

ABCA
CDDB
BDDC
ACBA
        于是我们便可以讨论ABCD这四个放在什么地方即可(表格中加粗的是样例的方案)。现在的关键是如何确定第k小。这让我很是纠结。这道题很容易使人想到进位制上面去,我也朝这个方向想了一下,确实没有思路。但是进位制却启发我,确定第k小很纠结,还不如把第k小转为第几大。那么怎么转呢,这个很简单。令tot=N*N/4; 那么k=4^tot+1-k;于是就转成第几大了。但是为了编程方便,将式子进行化简。k=(1LL<<2*tot)+1-k; 1后面一定要加LL,不然会出错(c++默认常数为int类型)。

        完成了这一步,剩下的就很简单了。我们现在需要确定的便是每一个1可以放在什么地方。很显然,既然要满足第k大, 那么当前位置可以填1的条件为:剩下所有格子的方案数(记为temp)>=K; 这样才能保证为第k大。如果不满足,则这一位填0。K-=temp

        最后扫完整个图,答案就求出来了。


【代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
const long long INF=1000000000000000005LL;
int Map[15][15],f[500],N,tot;    
//Map为每个位置的编号。同一编号的位置表示能旋转到达
//f的下标是编号,表示这个编号还有多少种可能的方案
bool ans[15][15],vis[500];
//ans记录答案
//vis的下标是编号,表示这个编号是否填过1(填过就不能再填了)
long long K;
long long _findc()   //根据乘法原理求方案总数
{
    long long sum=1;
    for(int i=1;i<=tot;i++)
        if(!vis[i]&&sum<INF) 
		    sum*=f[i];
    return sum;
}
void _init()
{
	scanf("%d%I64d",&N,&K);
}
void _solve()
{
	int x,y;
    for(int i=1;i<=N;i++)  //建图Map
        for(int j=1;j<=N;j++)
            if(!Map[i][j])
            {
                tot++; 
	            f[tot]=4;
	            x=i;
	            y=j;
                for(int k=0;k<4;k++)
                {
                    swap(x,y);
		            x=N-x+1;
		            Map[x][y]=tot;
	            }
            }
	long long temp;
    K=(1LL<<2*tot)+1-K;    //这里要加上LL,c++默认常数是int类型
    for(int i=1;i<=N;i++)
        for(int j=1;j<=N;j++)
            if(vis[Map[i][j]]==false)
            {
				vis[Map[i][j]]=true;  
				//在查询方案数前假设此位置已填1,标记
                temp=_findc();
                if(K<=temp) 
	                ans[i][j]=true;
	            else      
				{
					vis[Map[i][j]]=false;  //若不能填1,去掉标记
					f[Map[i][j]]--;
	                K-=temp;
				}
            }
    for(int i=1;i<=N;i++)    //输出答案
    {
        for(int j=1;j<=N;j++)
        {
            if(ans[i][j]) 
			    putchar('1'); 
			else 
			    putchar('0');
		}
		putchar('\n');
	}
}
int main()
{
	_init();
	_solve();
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值