闪烁

闪烁

农夫约翰对牛棚里昏暗的灯光感到不满,刚刚安装了一个新吊灯。

新吊灯由 $N$ 个灯泡组成,这 $N$ 个灯泡围成一圈,编号为 $0 \sim N−1$。

奶牛对这个新吊灯非常着迷,并且喜欢玩以下游戏:

对于第 $i$ 个灯泡,如果在 $T−1$ 时刻,它左侧的灯泡(当 $i>0$ 时,为第 $i−1$ 个灯泡;当 $i=0$ 时,为第 $N−1$ 个灯泡)是开着,那么在 $T$ 时刻,就切换这个灯泡的状态。

这个游戏将持续 $B$ 单位时间。

给定灯泡的初始状态,请确定在 $B$ 单位时间后,它们的最终状态。

输入格式

第一行包含两个整数 $N$ 和 $B$。

接下来 $N$ 行,按顺序描述每个灯泡的初始状态,每行包含一个整数 $1$ (表示开)或 $0$(表示关)。

输出格式

共 $N$ 行,按顺序每行输出一个灯泡的最终状态。

数据范围

$3 \leq N \leq 16$,
$1 \leq B \leq {10}^{15}$

输入样例:

5 6
1
0
0
0
0

输出样例:

1
1
1
0
1

样例解释

灯泡状态如下:

时刻 T=0: 1 0 0 0 0
时刻 T=1: 1 1 0 0 0
时刻 T=2: 1 0 1 0 0
时刻 T=3: 1 1 1 1 0
时刻 T=4: 1 0 0 0 1
时刻 T=5: 0 1 0 0 1
时刻 T=6: 1 1 1 0 1

 

解题思路

  可以发现,每个时刻灯泡亮暗状态的改变可以用二进制的位异或来表示,比如有第$t$个时刻时,第$i$个灯的状态为$s_{i}^{\left( t \right)}$,则下一个时刻的状态可以表示为$s_{i}^{\left( t+1 \right)} = s_{i}^{\left( t \right)} \wedge s_{i-1}^{\left( t \right)}$。

  一共最多有$16$个灯泡,那么最多有$2^{16}$种不同的状态,而变化的次数最多有${10}^{15}$,因此这个状态的转移必然存在一个环。

  可以用抽屉原理来证明,假设有$n$个灯泡,如果要变$2^n+1$次,而最多只有$2^n$种不同的状态,因此必然有两种状态是相同的,即必然在进行状态变化的时候会出现循环,即上图中的环。每个状态变化后的新状态是唯一的,对应上图就是每个结点都有唯一一个后继,因此必然存在某个点开始会出现环。

  因此我们在枚举状态的时候可以判断一下这个状态之前是否出现过,如果出现过那么表示我们找到一个环,此时我们用原来要循环的次数对这个环的大小取余数得到$r$,然后再从这个出现过点转移$r$次状态就可以得到最终的状态,这个状态与原本要循环的结果是一样的。

  我们一开始模拟状态的转移最多需要$2^{16}$的计算量,余数最大也是$2^{16}$,因此总的计算量最多为$2 \times 2^{16}$。

  其中状态的转移是用位运算来实现的,例如:

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 1 << 16;
 7 
 8 int mp[N];
 9 
10 int main() {
11     int n;
12     LL m;
13     scanf("%d %lld", &n, &m);
14     
15     int st = 0;
16     for (int i = 0; i < n; i++) {
17         int x;
18         scanf("%d", &x);
19         st |= x << i;
20     }
21     
22     memset(mp, -1, sizeof(mp)); // -1表示这个状态没有出现过
23     mp[st] = 0; // 初始状态为0
24     for (int i = 1; i < 1 << n && i <= m; i++) {    // 最多枚举2^n个状态或m次
25         st ^= ((st << 1) | (st >> n - 1 & 1)) & ((1 << n) - 1); // 从当前状态转移到下一个状态
26         // 后面的&((1 << n) - 1)表示将第n位开始往后的位都变成0,这些位都进行&0操作,前面0~n-1为进行&1操作
27         if (mp[st] != -1) { // 这个状态之前出现过,表明出现环
28             int cnt = i - mp[st];   // 计算环的大小
29             int r = (m - i) % cnt;  // 此时必然有m >= i,计算在环中要走的步数,即转移的次数
30             while (r--) {   // 转移r次
31                 st ^= ((st << 1) | (st >> n - 1 & 1)) & ((1 << n) - 1);
32             }
33             
34             for (int i = 0; i < n; i++) {
35                 printf("%d\n", st >> i & 1);
36             }
37             return 0;
38         }
39         mp[st] = i; // 当前状态是第i个状态
40     }
41     
42     // 执行到这里表明i == m + 1,当前的st就是第m个状态
43     for (int i = 0; i < n; i++) {
44         printf("%d\n", st >> i & 1);
45     }
46     
47     return 0;
48 }

 

参考资料

  AcWing 1960. 闪烁(寒假每日一题2022):https://www.acwing.com/video/3666/

posted @ 2022-06-09 15:06  onlyblues  阅读(80)  评论(0编辑  收藏  举报
Web Analytics