1.3 赢球票(★)

题目信息

2016 年国赛-程序设计题

C/C++ C组第4题

Java C组第4题

【问题描述】

某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。

主持人拿出 张卡片(上面写着 的数字),打乱顺序,排成一个圆圈。

你可以从任意一张卡片开始顺时针数数:

如果数到的数字刚好和卡片上的数字相同,则把该卡片收入囊中,从下一张卡片重新数数。

直到再无法收获任何卡片,游戏结束。囊中卡片数字的和就是赢得球票的张数。

比如卡片排列是 1 2 3,我们从 1 号卡开始数,就把 1 号卡拿走。再从 2 号卡开始,但数的数字无法与卡片对上,很快数字越来越大,不可能再拿走卡片了。因此这次我们只赢得了 1 张球票。

还不算太坏!如果我们开始就傻傻地从 2 或 3 号卡片数起,那就一张卡片都拿不到了。

如果运气好,卡片排列是 2 1 3,那我们可以顺利拿到所有的卡片!

本题目标:已知顺时针卡片序列,随便你从哪里开始数,求最多能赢多少张球票(就是收入囊中的卡片数字之和)。

【输入格式】

第一行一个整数 ,表示卡片数目。

第二行 个整数,表示顺时针排列的卡片。

【输出格式】

输出一行,一个整数,表示最好情况下能赢得多少张球票。

【样例输入】

3

1 2 3

【样例输出】

1

懒人速读

给定 张卡片,卡片上分别写着数字

现将这 张卡片围成一个圈。

我们可以从圈上任意一个位置开始顺时针数数:

若当前数到的数字和卡片上的数字相同,则将卡片取走,并从下一张卡片所在位置重新数数:

问我们取走的卡片上的数字之和最大可以是多少?

举例说明

,则从第一张卡片所在位置开始数数的过程如下。

题目分析

核心考点

枚举

拆环成链

这是一道十分经典的模拟题。 刚着手解决本题时,可能绝大部分人都会提出4个问题。

问题1.题目给定的是一个环(圆圈),我们需要如何模拟在环上的移动呢?

问题2. 如何表示被拿走的卡片呢?

问题3.从哪个位置(起点)开始数数能拿走最多的卡片呢?

问题4.游戏什么时候会结束呢?

下面依次对这4个问题进行分析。

对于问题1:

对于在序列上的移动,如果想从当前位置顺时针移动到下一个位置,只要让下标 +1 即可。但当处于第 个位置时,由于没有第 个位置,所以无法移动。而对于环,第 个位置即第 1 个位置,所以从第 个位置移动到下一个位置只要让下标为 1 即可,其他位置的移动和序列相同。

对于问题2:

可以对被拿走的卡片打上标记。定义 ,其中 表示第 张是否被取走( 表示被取走, 表示没有被取走)。下图展示了第 2 张卡片被拿走的情况。

对于问题3:

不知道。既然不知道,就将每个位置都作为一次起点并模拟整个过程,再从中选出最大的卡片和(即答案)。

对于问题4:

游戏结束分为以下两种情况。

取走所有的卡片(即取走 张卡片)。

当前数的数大于 (不可能再取走卡片了)。

解决了上述4个问题,就可以开始解题了。

按照上面的分析,我们需要完成以下几点。

(1) 枚举起点,并在每次枚举了一个起点后将所有卡片的 flag 初始化为 0。

(2) 有了起点后就可以模拟数数的过程,流程图如下。

判断游戏是否结束。

判断当前位置的卡片是否被取走(若被取走,则移动到下一个位置)。

判断当前位置的卡片的值是否和数的数的值相同(相同则取走该卡片,并重新开始数数)。

判断这次模拟得到的结果是否可以作为答案。

参考代码1-3 【赢球票】

 1   #include<bits/stdc++.h>
 2   using namespace std;
 3   const int N = 1e2 + 10;
 4   int n, a[N], flag[N];
 5   signed main(){
 6       cin >> n;
 7       for(int i = 1 ; i <= n ; i ++) cin >> a[i];
 8       int ans = 0; // ans 表示答案
 9       for(int i = 1 ; i <= n ; i ++){ // i 表示枚举的起点
10           // 将所有卡片的 flag 初始化为 0
11           for(int j = 1 ; j <= n ; j ++) flag[j] = 0; // flag[j] = 1 表示卡片被取走,反之没被取走
12           int num = 1 , pos = i , sum = 0, cnt = 0; // num 表示数的数的值,pos 表示当前位置,sum 表示以 i 为起点数数所能取走的卡片的和,cnt 表示拿走的卡片的数量
13   
14           // 开始模拟
15           while(1){
16               // 判断游戏是否结束
17               if(cnt == n || num > n) break; // 如果取走了所有卡片,或者数的数大于 n(不可能再取走卡片了),则游戏结束
18               // 判断当前位置的卡片是否被取走
19               if(flag[pos] == 1) { // 如果该卡片已被取走,则移动到下一个位置
20                   // 移动到下一个位置:如果当前位置 pos = n,则下一个位置为 1,否则下一个位置为 pos + 1
21                   if(pos == n) pos = 1;
22                   else pos ++;
23                   continue ;
24               }
25               // 数的数和当前位置卡片的值相同时取走该卡片(a[pos] 表示第pos张卡片的值)
26               if(num == a[pos]){
27                   sum += a[pos]; // 取走卡片的和 + a[pos]
28                   cnt ++;        // 取走的卡片个数 + 1
29                   num = 1;       // 取走卡片后要重新数数
30                   flag[pos] = 1; // 取走卡片后该卡片对应的 flag 置为 1
31                   // 移动到下一个位置:如果当前位置 pos = n,则下一个位置为 1,否则下一个位置为 pos + 1
32                   if(pos == n) pos = 1;
33                   else pos ++;
34               } else {
35                   // 数的数和当前位置卡片的值不相同时
36                   num ++ ; // 数的数的值 + 1
37                   // 移动到下一个位置:如果当前位置 pos = n,则下一个位置为 1,否则下一个位置为 pos + 1
38                   if(pos == n) pos = 1;
39                   else pos ++;
40               }
41           }
42           // 模拟结束,判断该模拟结果是否可以作为答案
43           if(sum > ans) ans = sum;
44       }
45       cout << ans << '\n';
46       return 0;
47   }