Codeforces 819 D. Mister B and Astronomers 数论

题意:

有T个石子,n个人,每个人轮流取石子,一颗石子只能取一次,求每个人能取到多少石子。
假设i-1号取的位置为pos,那么i号取的位置就是 (pos+ai)modT ,如果当前位置的石子已经被取走,这个人的所取石子数量不增加,i+1取的位置为 (pos+ai+ai+1)modT

算法:
  • S=ni=1ai
  • sti=ni=2ai ,为每个人第一次取得位置,不考虑那些第一次就取不到的人。那么每个人可以取到的就是 (sti+S)modT(sti+2S)modT...

将这些人放在一个环中,可以发现两两之间是没有交,那么怎么样可以更简单的计算答案,把环分成 gcd(S,T) ,环长就是 Tgcd(S,T) ,一个人可以取的石子就是与下一个人在所在环上距离。证明显然,其实是不会,感性愉悦下就好。

代码:
#include <cstdio>
#include <map>
#include <string.h>
#include <algorithm>
#include <cmath>

using namespace std;

int rd() {
    int x = 0; char c = getchar();
    while (c > '9' || c < '0') c = getchar();
    while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x;
}

void wt(int x) {
    if (x >= 10) wt(x / 10);
    putchar(x % 10 + 48);
}

void exgcd(int a, int b, int &x, int &y) {
    if (!b) { x = 1, y = 0; return; }
    exgcd(b, a % b, x, y);
    int t = x;
    x = y, y = t - a / b * y;
}

const int N = 2e5 + 10;

struct Pt{
    int bel, pos, id;
    Pt(int a = 0, int b = 0, int c = 0) : bel(a), pos(b), id(c) {}
    bool operator < (const Pt &t) const {
        return bel < t.bel || (bel == t.bel && pos < t.pos);
    }
} c[N];
map <int, int> mp;
int T, n, a[N], ans[N], S, cnt;

int main() {
    T = rd(), n = rd();
    for (int i = 1; i <= n; i ++) a[i] = rd(), S = (S + a[i]) % T;
    int t = -a[1];
    for (int i = 1; i <= n; i ++) {
        t = (t + a[i]) % T;
        if (!mp[t]) mp[t] = i;
    }
    int g = __gcd(S, T), len = T / g, step = S / g, inv, y;
    //printf("%d %d %d\n", S, T, g);
    t = -a[1], exgcd(step, len, inv, y);
    inv = (inv + len) % len;
    for (int i = 1; i <= n; i ++) {
        t = (t + a[i]) % T;
        if (mp[t] == i) c[++cnt] = Pt(t % g, 1ll * (t / g) * inv % len, i);
    }
    sort(c + 1, c + cnt + 1);
    for (int i = 1; i <= cnt; i ++) {
        int j = i;
        while (j < cnt && c[j + 1].bel == c[i].bel) j ++;
        //printf("%d\n", j);
        for (int k = i; k < j; k ++) ans[c[k].id] += c[k + 1].pos - c[k].pos;
        ans[c[j].id] += c[i].pos + len - c[j].pos;
        i = j;
    }
    for (int i = 1; i <= n; i ++) wt(ans[i]), putchar(32); puts("");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值