Codeforces Round 933 (Div. 3) D. Rudolf and the Ball Game【模拟+set对集合去重】

原题链接:https://codeforces.com/contest/1941/problem/D

每次测试时限:2 秒

每次测试的内存限制:256 兆字节

输入:标准输入

输出:标准输出

题目描述:

鲁道夫和伯纳德决定和朋友们玩一个游戏。 n 人站成一圈,开始互相扔球。他们按顺时针顺序从 1 到 n 依次编号。

让我们把球从一个人向他的邻居移动称为一次过渡。转换可以顺时针或逆时针进行。

我们把从棋手 y1 到棋手 y2 的顺时针(逆时针)距离称为从棋手 y1 到棋手 y2 所需的顺时针(逆时针)转换次数。例如,如果是 n=7 ,那么从 2 到 5 的顺时针距离是 3 ,而从 2 到 5 的逆时针距离是 4 。

初始时,球在编号为 x 的棋手处(棋手按顺时针方向编号)。在第 i 步时,持球人将球抛向 ri ( 1≤ri≤n−1 )顺时针或 7 ( 2 )逆时针的距离。( 1≤ri≤n−1) 的距离顺时针或逆时针抛出。例如,如果有 7 名球员,第 2 名球员在接球后将球投掷到 5 处,那么球将被第 7 名球员(顺时针投掷)或第 4 名球员(逆时针投掷)接住。该示例的图示如下。

由于下雨,比赛在 m 次投掷后中断。雨停后,大家又聚在一起继续比赛。但是,没有人记得球在谁手里。结果,伯纳德记住了每次投掷的距离和****次投掷的方向(顺时针或逆时针)。

鲁道夫请你帮助他,根据伯纳德提供的信息,计算出 m 次抛球后可能拿到球的球员人数。

输入输出描述:

输入

输入的第一行包含一个整数 t ( 1≤t≤10^4 ) - 测试用例的数量。然后是测试用例的说明。

每个测试用例的第一行都包含三个整数 n,m,x ( 2≤n≤1000 , 1≤m≤1000 , 1≤x≤n ) - 分别是球员人数、投球次数和最先投球球员的号码。

接下来的 m 行按顺序包含每次投掷的信息。每一行都包含一个整数 ri ( 1≤ri≤n−1 )--距离。( 1≤ri≤n−1 )--进行 i 次抛球的距离,以及一个符号 ci ,等于 "0"、"1 "或"?

  • 如果 ci ='0',那么 i 次投掷是顺时针进行的、
  • 如果 ci ='1',那么 i 次投掷是逆时针方向、
  • 如果 ci ='?',那么伯纳德不记得方向, i 次投掷可能是顺时针,也可能是逆时针。

可以保证总和 n⋅m ( n 乘以 m )是顺时针或逆时针。 n 乘以 m )不超过 2⋅10^5。

输出

每个测试用例输出两行。

第一行,输出游戏结束时可能拥有球的玩家 k ( 1≤k≤n ) 的数量。

下一行输出 bi ( 1≤bi≤n )--按递增顺序排列的球员号码。所有数字必须不同。

Example
input
5
6 3 2
2 ?
2 ?
2 ?
12 1 2
3 1
10 7 4
2 ?
9 1
4 ?
7 0
2 0
8 1
5 ?
5 3 1
4 0
4 ?
1 ?
4 1 1
2 ?

output
3
2 4 6 
1
11 
4
3 5 7 9 
3
2 3 5 
1
3 

**注**

下面是第一个测试案例中三次投掷的示意图。箭头表示可能的投掷方向。投掷后可能持球的球员用灰色标出。

解题思路:

首先对于已经知道方向的投掷我们可以不需要再考虑,因为这些操作的影响是固定的,关键需要考虑的就是那些方向不明确的操作,对于每一个方向不明确的操作,可以是顺时针投掷的,也可以是逆时针投掷的,那么对于每一个方向不明确的操作就会有俩种选择,我们对于一些已有的位置,利用当前这个位置进行扩展,那么每一个方向不明确的投掷,就会有俩种选择,m=1000,那么最多就会有2^1000种选择,这样看起来这个时间复杂度是非常大的,肯定过不了,但是我们可以发现实际上有效的位置最多只有n=1000种位置,也就是操作的过程中某些位置会出现多次,也就是会有一些无效的操作,实际上对于每一个位置,如果出现了多次,我们只需要保留一次即可,这样就只最多只会有1000种位置状态,就把原来的2^1000种状态大大的缩小了,此时每次操作最多会有1000种状态,最多有1000次操作,那么时间大概就是1000*1000=1e6,这个时间复杂度就比较小了,是可以过的,对于上述某些位置出现多次,只需要维护一次即可,其他的多出来的次数需要丢弃掉,也就是需要去重,我们考虑使用set集合去重即可。

时间复杂度:O(n*m),m次操作,每次操作最多n个有效状态。

空间复杂度:O(n),使用一个set来维护这一过程。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <cmath>
#include <set>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int N = 1e5 + 10, M = N * 2, mod = 1e9 + 7;
int T, n, m, a;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        cin >> n >> m >> a;
        set<int> q[2]; // q[0]所有元素扩展出来的放入q[1],然后清空q[0]
        int ix = 0;    // q[1]所有元素扩展出来的放入q[0],然后清空q[1]
        q[ix].insert(a);
        while (m--)
        {
            int x;
            char ch;
            cin >> x >> ch;
            while (q[ix].size())
            {
                int u = *(q[ix].begin()); // 一边遍历一边删除,非常有用的技巧,使用for循环一遍遍历set一遍删除是不可以的,但是这里这种方式是可以的
                q[ix].erase(u);
                if (ch == '0' || ch == '?') // 顺时针
                {
                    q[ix ^ 1].insert((u + x - 1) % n + 1);
                }
                if (ch == '1' || ch == '?') // 逆时针
                {
                    q[ix ^ 1].insert((u - x - 1 + n) % n + 1);
                }
            }
            ix ^= 1; // 采取异或操作,只需要使用一个大小为2的set维护即可,相比直接使用2个set,这种写法非常巧妙,可以减少代码量
        }
        cout << q[ix].size() << '\n'; // 此时的q[ix]就是最终可以扩展出来的所有位置
        for (auto &t : q[ix])
        {
            cout << t << ' ';
        }
        cout << '\n';
    }
    return 0;
}
  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值