2017 Multi-University Training Contest - Team 8

打多校8场以来,第一场差点给零封的场。。全场1000+支队给零封了600支。。可怕

Battlestation Operational

一道好莫比乌斯题。
题意:
要求1到n内,满足$gcd(i, j) == 1且 i > j 的\sum\left \lceil \frac {i}{j}\right \rceil $
其实要满足gcd(i, j) == 1只需要拿莫比乌斯反演一下就好了。
因为莫比乌斯满足
f ( n ) = ∑ d ∣ n u ( d ) ∗ F ( n d ) f(n) = \sum_{d | n} u(d) * F(\frac{n}{d}) f(n)=dnu(d)F(dn)
所以我们只需要把F(n)求出来就好了。
$F(n) = \sum_{i = 1}^{n}\sum_{j = 1}^{i}\left \lceil \frac {i}{j}\right \rceil $
但这样求F(n)会成 n 2 n^2 n2

所以,对于求1到n的F(n),有递推公式
我们设f(n)为1到n的 i j \frac{i}{j} ji向下取整的和
我们设F(n)为1到n的 i j \frac{i}{j} ji向上取整的和
我们可以得到:
F(i) = f(i - 1) + i
f(i) = F(i) - i + cnt(i的因子数)
这样的递推公式
就可以快速求得F(n)了。打表即可

#include <bits/stdc++.h>

#define MAXN 1000005
#define MOD 1000000007

using namespace std;

int mu[MAXN];
int prime[MAXN];
int tot;
int f[MAXN], F[MAXN];
bool flag[MAXN];

void moblus() {
    mu[1] = 1;
    tot = 0;
    for (int i = 2; i < MAXN; i++) {
        if (!flag[i]) {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for (int j = 0; j < tot; j++) {
            if (i * prime[j] >= MAXN) {
                break;
            }
            flag[i * prime[j]] = true;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            } else {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}

void init() {
    moblus();    
    f[1] = F[1] = 1;
    for (int i = 2; i < MAXN; i++) {
        F[i] = f[i - 1] + i;
        int x = i, ans = 1;
        for (int j = 0; prime[j] * prime[j] <= i; j++) {
            int cnt = 1;
            while (x % prime[j] == 0) {
                cnt++;
                x /= prime[j];
            }
            ans *= cnt;
        }
        if (x > 1) {
            ans *= 2;
        }
        f[i] = F[i] - i + ans;
    }
    for (int i = 2; i < MAXN; i++) {
        f[i] = F[i];
    }
    for (int i = 2; i < MAXN; i++) {
        for (int j = i; j < MAXN; j += i) {
            F[j] += mu[i] * f[j / i];
        }
    }
    for (int i = 2; i < MAXN; i++) {
        F[i] += F[i - 1];
        F[i] %= MOD;
    }
}

int main() {
    init();
    int n;
    while (~scanf("%d", &n)) {
        printf("%d\n", F[n]);
    }
}

##Death Podracing##

比赛的时候没读过的相对水题。

题意:
有一个操场之类的东西长度为L,有n个人在上面跑,告诉他们的起始位置和速度给你,如果两个人相撞,能量小的那个人会被淘汰,输入顺序决定能力值的大小。
现在问最后剩一个人的时间是多少,时间要用分数表示。

思路:
其实很容易能想到,每个人会发生事故只会跟他在位置的前一个或者后一个相撞。
那我们可以用双指针维护他的前一个人的下标和后一个人的下表。
用优先队列维护发生事故的时间,两个人如果发生事故,那就比较一下哪个会被淘汰。模拟即可。

#include <bits/stdc++.h>

#define ll long long
#define MAXN 200005

using namespace std;

struct pop {
    ll d, v, idx;
    bool operator < (const pop &a) const {
        return d < a.d;
    }
} group[MAXN];

struct node {
    ll pre, nxt;
    double time;
    bool operator < (const node &a) const {
        return time > a.time;
    }
    node() {}
    node(ll p, ll n, double t) : pre(p), nxt(n), time(t) {}
};

priority_queue<node> q;
ll len;
ll L[MAXN], R[MAXN];
bool vis[MAXN];

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b); 
}

double getTime(ll x, ll y) {
    ll dx = (group[y].d - group[x].d + len) % len;
    ll dv = group[x].v - group[y].v;
    if (dv < 0) {
        dv = -dv;
        dx = len - dx;
    }
    return dx * 1.0 / dv;
}

void output(ll x, ll y) {
    ll dx = (group[y].d - group[x].d + len) % len;
    ll dv = group[x].v - group[y].v;
    if (dv < 0) {
        dv = -dv;
        dx = len - dx;
    }
    ll p = gcd(dx, dv);
    dx /= p;
    dv /= p;
    printf("%lld/%lld\n", dx, dv);
}

int main() {
    int T, n;
    scanf("%d", &T);    
    while (T--) {
        while (!q.empty()) {
            q.pop();
        }
        scanf("%d %lld", &n, &len);
        for (int i = 0; i < n; i++) {
            scanf("%lld", &group[i].d);
        }
        for (int i = 0; i < n; i++) {
            scanf("%lld", &group[i].v);
            group[i].idx = i;
        }
        sort(group, group + n);
        for (int i = 0; i < n; i++) {
            double t = getTime(i, (i + 1) % n);
            q.push(node(i, (i + 1) % n, t));
            L[i] = (i - 1 + n) % n;
            R[i] = (i + 1) % n;
        }
        memset(vis, false, sizeof(vis));
        node tmp;
        while (q.size() > 1) {
            tmp = q.top();
            q.pop();
            ll l = tmp.pre, r = tmp.nxt;
            if (vis[l] || vis[r]) {
                continue;
            }
            if (L[l] == r && R[r] == l) {
                break;
            }
            if (group[l].idx > group[r].idx) {
                vis[r] = true;
                R[l] = R[r];
                L[R[r]] = l;
                double t = getTime(l, R[r]);
                q.push(node(l, R[r], t));
            } else {
                vis[l] = true;
                L[r] = L[l];
                R[L[l]] = r;
                double t = getTime(L[l], r);
                q.push(node(L[l], r, t));
            }
            if (--n <= 0) {
                break;
            }
        }
        tmp = q.top();
        output(tmp.pre, tmp.nxt);
    }
}

##Hybrid Crystals##

比赛的时候没读的最水题。最后一个钟把一道给带错榜的组合数学题过了之后。。没心情读这题题目了。。这题毫无疑问,是一道阅读题。。。。

题意:
有三种数,一种是可以为正或者负的,一种只能为正,一种只能为负,现在问用这些数能不能表达出数k。

其实题目一大片都是一堆废话。。最有用的一句就是 a 1 = 1 , b 1 = N a_1 = 1, b_1 = N a1=1,b1=N
这句话的意思就是,我保证给你一个[-1, 1]的区间。。。
那每次进来数只需要维护这个区间的左右两边即可。因为一个区间加上一个数,区间一定是扩增这个数的长度的。最后只要判断k是否在这个区间内就好了

orz全场最水题,结果给带歪榜
(L和D写反了也过了,这数据要有多水)

#include <bits/stdc++.h>

#define MAXN 1005
#define ll long long

using namespace std;

struct node {
    ll val;
    char c;
} num[MAXN];

int main() {
    int n, m;
    int T;
    ll k;
    scanf("%d", &T);
    while (T--) {
        num[0].c = 'X';
        scanf("%d %lld", &n, &k);
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &num[i].val);
        }
        getchar();
        for (int i = 1; i <= n; i++) {
            scanf("%c", &num[i].c);
            
            getchar();
        }
        ll l = -1, r = 1;
        for (int i = 1; i <= n; i++) {
            if (num[i].c == 'N') {
                l -= num[i].val;
                r += num[i].val;
            } else if (num[i].c == 'L') {
                l -= num[i].val;
            } else if (num[i].c == 'D') {
                r += num[i].val;
            }
        }
        if (k <= r && k >= l) {
            puts("yes");
        } else {
            puts("no");
        }
    }
} 

##Killer Names##

本场导致差点给0封的题,开场10分钟,我对队友说,怎么这题还没有dalao过,简单组合数题啊。orz结果最后推到意识模糊4个钟后才过。。。

题意:
给first name 和last name,他们都由n长度的m个字符随机组成。
如果first name和last name之间没有出现过相同的字符,则称他们为Killer Names
现在问能组合出多少种这样的名字

思路:
死怼容斥结果硬生生怼不掉。
其实可以转换成高中的组合数学题。
n种颜色在m个位置上涂色,这n种颜色必须都用上的情况有多少种。
有递推公式
dp[i][j] = (j * (dp[i - 1][j] + dp[i - 1][j - 1]))
然后处理一下i,j的其他情况
i == j时有dp[i][j] = fac[i]

然后暴力枚举每一种情况相乘即可。。

#include <bits/stdc++.h>

#define ll long long
#define MAXN 2050

using namespace std;

const ll MOD = 1e9 + 7;

ll dp[MAXN][MAXN];
ll fac[MAXN];
ll c[MAXN][MAXN];

void init() {
    ll i, j;
    dp[0][0] = dp[1][0] = dp[0][1] = c[0][0] = fac[0] = 1;
    for (i = 1; i < MAXN; i++) {
        fac[i] = (fac[i - 1] * i) % MOD;
        c[i][0] = c[i][i] = 1;
        dp[i][i] = fac[i];
        for (j = 1; j < i; j++) {
            if (j > i) {
                dp[i][j] = 0;
            } else if (j == 1) {
                dp[i][j] = 1;
            } else {
                dp[i][j] = (j * (dp[i - 1][j] + dp[i - 1][j - 1]) % MOD) % MOD;
            }
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
        }
    }
}

int main() {
    init();
    int T;
    ll n, m, ans;
    while (~scanf("%d", &T)) {
        while (T--) {
            scanf("%lld %lld", &n, &m);
            ans = 0;
            if (m == 1) {
                puts("0");
                continue;
            }
            for (ll i = 1; i < m; i++) {
                for (ll j = 1; j <= m - i; j++) {
                    ans += (((c[m][i] * c[m - i][j]) % MOD) * ((dp[n][i] * dp[n][j]) % MOD)) % MOD;
                    ans %= MOD;
                }
            }
            printf("%lld\n", ans);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值