【NOIP模拟 提高组】外星密码

【问题描述】
小 W 潜入了外星人的基地刺探情报,遇到一个二级密码系统。一级密码是一个长度为
n 的 0-1 序列 B, 记为(b1 b2 ⋯ bn)。 将一级密码的第一位放到最后,得到一个新的序
列(b2 b3 ⋯ b1), 继续做同样的操作得到(b3 b4 ⋯ b2),如此反复,总共可以得到
n 个序列,将这些序列按字典序排序后,字典序最小的即为二级密码,输入这个二级密
码,就可以得到情报。
外星人将领将排序过后的 n 个字符串按照字典序从小到大的顺序逐行写成了一个 n 阶
矩阵,而且小 W 恰好看到了这个矩阵,不难发现,这个矩阵的第一行就是二级密码,所以
如果小 W 记住这个矩阵的第一行,就可以直接获得情报。但是小 W 在出发前听上司布置
任务时走神将“记住第一行”听成了“记住最后一列”。所以小 W 现在只记得最后一列是
什么,小 W 赶紧联系远在地球的你,希望你能帮他由矩阵的最后一列得到矩阵的第一行。
【输入格式】
共两行,第一行一个整数 n,第二行 n 个值为 0 或 1 的整数,表示矩阵的最后一列。
【输出格式】
输出文件共一行,为矩阵的第一行。
【样例】
5
1 1 0 0 0
0 0 1 0 1
【数据规模与约定】
对于 20%的测试数据n ≤ 20
对于 50%的测试数据 n≤ 1000
对于 100%的测试数据n ≤ 10000
【样例解释】
一级密码为(0 1 0 0 1),因此得到 5 个序列为:
0 1 0 0 1
1 0 0 1 0
0 0 1 0 1
0 1 0 1 0
1 0 1 0 0
经过排序过后的矩阵为:
0 0 1 0 1
0 1 0 0 1
0 1 0 1 0
1 0 0 1 0
1 0 1 0 0

分析:看到题,首先想到的一定是搜索,但是一看数据范围,n<=10000那就一定不是搜索了。所以还是从模拟入手找规律。

有两个重要的规律,此处详解:

设一级密码为 b1,b2,b3.b4,b5,b6
则所有一级密码为:

b1 b2 b3 b4 b5
b2 b3 b4 b5 b1
b3 b4 b5 b1 b2
b4 b5 b1 b2 b3
b5 b1 b2 b3 b4

可以将样例带入b1,b2,b3,b4,b5
排序可得矩阵:

b3 b4 b5 b1 b2
b1 b2 b3 b4 b5
b4 b5 b1 b2 b3
b2 b3 b4 b5 b1
b5 b1 b2 b3 b4

现在就得到了这样一个矩阵,因为题设含义为字典序从小到大,所以不难发现第一个规律:

b3<=b4<=b5<=b1<=b2

它的意思就是通过给定最后一列得出第一列有几个0,则第一列前面全为0,后面全为1。

通过这种方法,样例不难得出第一列为:

0
0
0
1
1

然后,我们画出第一列和最后一列:

0      1
0      1
0      0
1      0
1      0

观察发现第一列的第一个0对应最后一列第一个0,第一列第二个0对应最后一列第二个0……1的规律也是如此。

这里用另一组数据给出证明,若输入

6
1 1 1 0 1 0

则可以得出第一列和最后一列:

0      1
0      1
1      1
1      0
1      1
1      0

按照如上结论第一列第一行的0对应最后一列第四行的0,第一列第二行的0对应最后一列第六行0

为什么是第一行的0对应第四行而不是第六行的0呢?
因为每行已按照字典序从小到大排序,说明第一行中间四个数字一定小于第二行中间四个数字,第四行和第六行也是如此。所以小的对应小的,而不是小的对应大的。

找到这两个规律,接下来就好办了。
还是以上面样例。因为求的数组相当于第一行,所以甚至不需要开二维数组,现在设第一行6个数:

0 a b c d 1

则按照一次密码变换之后为:

a b c d 1 0

因为上面提到此处末尾的0对应第一个0,所以这6个数的顺序和第四行顺序一致
故:a=1

因为此时为:

1 b c d 1 0

且此时为第四行的1,因为第四行的1对应最后一列第二行的1

所以:

b c d 1 0 1

顺序与第二行一致,故b=0

以次类推。

至此,此题就可以解出来了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int hang[10001],lie_last[10001],lie_first[10001],a1[10001]= {1},a0[10001]= {1};
int ref[10001];
int num=0;
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        cin>>lie_last[i];
        if(lie_last[i]==0) num++;
    }
    for(int i=1; i<=n; i++)
    {
        if(num>0) lie_first[i]=0;
        else lie_first[i]=1;
        num--;
    }
    int re_0=1,re_1=1;
    for(int i=1; i<=n; i++)
    {
        if(lie_last[i]==0) a0[a0[0]]=i,a0[0]++;
        else a1[a1[0]]=i,a1[0]++;
    }
    for(int i=1; i<=n; i++)
    {
        if(lie_first[i]==0) ref[i]=a0[re_0],re_0++;
        else if(lie_first[i]==1) ref[i]=a1[re_1],re_1++;

    }
    hang[1]=lie_first[1];
    hang[n]=lie_last[1];
    cout<<hang[1]<<" ";
    int k=1;
    for(int i=2;i<n;i++){
        cout<<lie_first[ref[k]]<<" ";
        k=ref[k];
    }
    cout<<hang[n];
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值