月度归档: 2018 年 4 月

[国家集训队]礼物 题解

[国家集训队]礼物 题解

题目地址:洛谷:【P2183】[国家集训队]礼物 – 洛谷、BZOJ:Problem 2142. — 礼物

题目描述

一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。由于方案数可能会很大,你只需要输出模P后的结果。

输入输出格式

输入格式:
输入的第一行包含一个正整数P,表示模;
第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;
以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

输出格式:
若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。

输入输出样例

输入样例#1:

100
4 2
1
2

输出样例#1:

12

输入样例#2:

100
2 2
1
2

输出样例#2:

Impossible

说明

【样例说明】
下面是对样例1的说明。
以“/”分割,“/”前后分别表示送给第一个人和第二个人的礼物编号。12种方案详情如下:
1/23 1/24 1/34
2/13 2/14 2/34
3/12 3/14 3/24
4/12 4/13 4/23
设P=p1^c1 * p2^c2 * p3^c3 * … * pt ^ ct,pi为质数。
对于15%的数据,n≤15,m≤5,pi^ci≤10^5;
在剩下的85%数据中,约有60%的数据满足t≤2,ci=1,pi≤10^5,约有30%的数据满足pi≤200。
对于100%的数据,1≤n≤10^9,1≤m≤5,1≤pi^ci≤10^5,1≤P≤10^9。

题解

本题需要的数学姿势有:数学笔记:数论(Lucas、CRT) | KSkun’s Blog
参考资料:题解 P2183 【[国家集训队]礼物】 – 没名字可被用的博客 – 洛谷博客
无解仅当\sum_{i=1}^m w_i < n
有解的情况下,要求的就是\mathrm{C}_n^{w_1} \times \mathrm{C}_{n-w_1}^{w_2} \times \cdots \bmod P,对于每个组合数,应用扩展Lucas定理求解即可。

代码

// Code by KSkun, 2018/4
#include <cstdio>

typedef long long LL;

struct Num {
    LL num, pcnt; // pcnt: p因子数量,num: 提取p因子后乘起来的积
    Num(LL num = 0, LL pcnt = 0): num(num), pcnt(pcnt) {}
};

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005, MAXM = 10;
const LL INF = 1e15;

inline LL fpow(LL n, LL k, LL p) {
    LL res = 1; n %= p;
    while(k) {
        if(k & 1) res = res * n % p;
        n = n * n % p;
        k >>= 1;
    }
    return res;
}

LL P, n, m, w[MAXM];

// calculate prime divisors in x
LL divi[MAXN], dcnt[MAXN], dpow[MAXN], dtot;

inline void calDivisor(LL x) {
    dtot = 0;
    for(LL i = 2; i * i <= P; i++) {
        if(x % i == 0) {
            divi[++dtot] = i;
            while(x % i == 0) {
                x /= i; dcnt[dtot]++;
            }
            dpow[dtot] = fpow(i, dcnt[dtot], INF);
        }
    }
    if(x != 1) {
        dtot++; 
        divi[dtot] = dpow[dtot] = x; dcnt[dtot] = 1;
    }
}

// calculate x! mod p^k
inline Num calFactorial(LL x, LL id) {
    if(x == 1 || x == 0) return Num(1, 0); // 1! = 0! = 1
    LL pk = dpow[id], res = 1;
    LL pnum = 0;
    for(LL i = x; i; i /= divi[id]) pnum += i / divi[id]; // 计算p因子的数量
    Num nxt = calFactorial(x / divi[id], id); // 递归计算提取了一个p因子的p倍数组成的阶乘
    res = res * nxt.num % pk; // 合并答案
    if(x >= pk) { // 如果有多段超过p^k的,预处理应用快速幂
        LL fac = 1;
        for(LL i = 1; i < pk; i++) {
            if(i % divi[id] == 0) continue;
            fac = fac * i % pk;
        }
        res = res * fpow(fac, x / pk, pk) % pk;
    }
    for(LL i = x; i >= 1; i--) { // 非整段p^k,暴力求解
        if(i % pk == 0) break;
        if(i % divi[id] == 0) continue;
        res = res * i % pk;
    }
    return Num(res, pnum);
}

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

inline LL calInverse(LL n, LL p) {
    LL x, y;
    exgcd(n, p, x, y);
    return (x % p + p) % p;
}

LL crtm[MAXN];

// CRT求解组合数
inline LL crt() {
    LL res = 0;
    for(int i = 1; i <= dtot; i++) {
        res = (res + crtm[i] * P / dpow[i] % P * calInverse(P / dpow[i], dpow[i]) % P) % P;
    }
    return res;
}

int main() {
    P = readint(); n = readint(); m = readint();
    LL wsum = 0;
    for(int i = 1; i <= m; i++) {
        w[i] = readint();
        wsum += w[i];
    }
    if(wsum > n) {
        puts("Impossible"); return 0;
    }
    calDivisor(P);
    LL ans = 1;
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= dtot; j++) {
            Num N = calFactorial(n, j), 
                M = calFactorial(w[i], j), 
                NM = calFactorial(n - w[i], j);
            // 分别代表n!、m!、(n-m)!
            N.pcnt -= M.pcnt + NM.pcnt;
            if(N.pcnt >= dcnt[j]) { // 如果p因子更多,说明能整除,模意义下为0
                crtm[j] = 0;
            } else {
                crtm[j] = N.num * calInverse(M.num, dpow[j]) % dpow[j] 
                    * calInverse(NM.num, dpow[j]) % dpow[j] 
                    * fpow(divi[j], N.pcnt, dpow[j]) % dpow[j]; // 把p因子乘进去
            }
        }
        ans = ans * crt() % P;
        n -= w[i];
    }
    printf("%lld", ans);
    return 0;
}
[USACO15DEC]最大流Max Flow 题解

[USACO15DEC]最大流Max Flow 题解

题目地址:洛谷:【P3128】[USACO15DEC]最大流Max Flow – 洛谷

题目描述

FJ给他的牛棚的N(2≤N≤50,000)个隔间之间安装了N-1根管道,隔间编号从1到N。所有隔间都被管道连通了。
FJ有K(1≤K≤100,000)条运输牛奶的路线,第i条路线从隔间si运输到隔间ti。一条运输路线会给它的两个端点处的隔间以及中间途径的所有隔间带来一个单位的运输压力,你需要计算压力最大的隔间的压力是多少。

输入输出格式

输入格式:
The first line of the input contains N and K .
The next N−1 lines each contain two integers x and y (x≠y) describing a pipe between stalls x and y .
The next K lines each contain two integers s and t describing the endpoint stalls of a path through which milk is being pumped.

输出格式:
An integer specifying the maximum amount of milk pumped through any stall in the barn.

输入输出样例

输入样例#1:

5 10
3 4
1 5
4 2
5 4
5 4
5 4
3 5
4 3
4 3
1 3
3 5
5 4
1 5
3 4

输出样例#1:

9

题解

路径加考虑做成树上差分,倍增LCA处理LCA问题。最后把差分加起来得到真实值,统计最大值即可。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>
#include <cmath> 

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 50005, INF = 1e9;

int n, k;

struct Edge {
    int to, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void addedge(int u, int v) {
    gra[tot] = Edge {v, head[u]}; head[u] = tot++;
}

int lgn, anc[MAXN][20], dep[MAXN];

inline void dfs(int u) {
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(v == anc[u][0]) continue;
        anc[v][0] = u;
        dep[v] = dep[u] + 1;
        dfs(v);
    }
}

inline void calanc() {
    for(int j = 1; (1 << j) <= n; j++) {
        for(int i = 1; i <= n; i++) {
            anc[i][j] = anc[anc[i][j - 1]][j - 1];
        }
    }
}

inline int querylca(int u, int v) {
    if(dep[u] > dep[v]) std::swap(u, v);
    int del = dep[v] - dep[u];
    for(int i = 0; (1 << i) <= del; i++) {
        if((1 << i) & del) v = anc[v][i];
    }
    if(u == v) return u;
    for(int i = lgn; i >= 0; i--) {
        if(anc[u][i] != anc[v][i]) {
            u = anc[u][i];
            v = anc[v][i];
        }
    }
    return anc[u][0];
}

int x, y, sum[MAXN], ans;

inline void dfs2(int u) {
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(v == anc[u][0]) continue;
        dfs2(v);
        sum[u] += sum[v];
    }
    ans = std::max(ans, sum[u]);
}

int main() {
    memset(head, -1, sizeof(head));
    n = readint(); k = readint();
    lgn = log(n) / log(2);
    for(int i = 1; i < n; i++) {
        x = readint(); y = readint();
        addedge(x, y); addedge(y, x);
    }
    dfs(1);
    calanc();
    while(k--) {
        x = readint(); y = readint(); int lca = querylca(x, y);
        sum[x]++; sum[y]++; sum[lca]--; sum[anc[lca][0]]--;
    }
    dfs2(1);
    printf("%d", ans);
    return 0;
}
[JSOI2011]任务调度 题解

[JSOI2011]任务调度 题解

题目地址:BZOJ:Problem 5179. — [Jsoi2011]任务调度

题目描述

一台超级计算机共有N颗CPU。现在这台超级计算机有M个任务要做,但同时还要考虑到不能让CPU过热。所幸的是这台超级计算机已经将任务安排好了,现在要做的只是请你根据安排好的指令来模拟它的工作过程。一开始,这N颗CPU都没有被分配任何的任务。之后,会给你以下几类指令(CPU的编号为1到N的整数,任务的编号为1到M的整数)
指令格式 作用
ADD n k w 将 k 号任务(权值为 w)分配给 n 号 CPU
DEC n k w 将 k 号任务的权值减少 w(已知 k 号任务被分配给了 n 号 CPU)
TRANS n1 n2 将分配给 n1 号 CPU 的任务全部转移给 n2 号 CPU
MIN n 输出分配给 n 号 CPU 的任务中权值最小的任务的权值
WORK n w 将分配给 n 号 CPU 的任务中权值最小的任务的权值加上 w,如果权值最小的任务不唯一,则不更改权值,并输出一行“ ERROR”

输入输出格式

输入格式:
包含N+1行。
第1行包含三个正整数N、M、K,分别表示CPU的数目、任务数和指令数。
第2行到K+1行,每行包含一条指令。
N≤500, M≤300000, K≤300000。
保证任务的权值在 32 位有符号整型的范围内。
保证一个任务只会被分配一次(即至多被 ADD 一次)。
保证 ADD 指令、DEC 指令和 WORK 指令中的 w 是非负整数。
保证 TRANS 指令的两个参数不相同。

输出格式:
若干行,其中包括MIN语句的输出和“ERROR”输出,每个输出占一行

输入输出样例

输入样例#1:

2 3 13
ADD 1 2 100
ADD 1 1 90
MIN 1
WORK 1 20
TRANS 1 2
MIN 2
ADD 1 3 105
TRANS 2 1
MIN 1
DEC 1 3 200
MIN 1
DEC 1 1 205
WORK 1 15

输出样例#1:

90
100
100
-95
ERROR

题解

考虑左偏树来做这个题。ADD即新建节点并且合并到该CPU堆。DEC即先删点,改完权值以后再并回去。TRANS即合并两个堆。MIN即询问堆中最小值。WORK同DEC。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cctype>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline char readop() {
    char c;
    while(!isalpha(c = fgc()));
    return c;
}

const int MAXN = 300005;

struct Node {
    int fa, val, dis, ch[2];
} tr[MAXN];

inline int merge(int u, int v) {
    if(!u || !v) return u + v;
    if(tr[u].val > tr[v].val) std::swap(u, v);
    tr[u].ch[1] = merge(tr[u].ch[1], v);
    tr[tr[u].ch[1]].fa = u;
    if(tr[tr[u].ch[0]].dis < tr[tr[u].ch[1]].dis) std::swap(tr[u].ch[0], tr[u].ch[1]);
    tr[u].dis = tr[tr[u].ch[1]].dis + 1;
    return u;
}

inline bool isleft(int p) {
    return tr[tr[p].fa].ch[0] == p;
}

inline void erase(int p) {
    tr[tr[p].ch[0]].fa = tr[tr[p].ch[1]].fa = 0;
    int q = merge(tr[p].ch[0], tr[p].ch[1]);
    if(q) tr[q].fa = tr[p].fa; if(tr[p].fa) tr[tr[p].fa].ch[!isleft(p)] = q;
    for(int i = tr[p].fa; i; i = tr[i].fa) {
        if(tr[tr[i].ch[0]].dis < tr[tr[i].ch[1]].dis) std::swap(tr[i].ch[0], tr[i].ch[1]);
        if(tr[i].dis == tr[tr[i].ch[1]].dis + 1) break;
        tr[i].dis = tr[tr[i].ch[1]].dis + 1;
    }
    tr[p].fa = tr[p].ch[0] = tr[p].ch[1] = tr[p].dis = 0;
}

inline int poprt(int p) {
    tr[tr[p].ch[0]].fa = tr[tr[p].ch[1]].fa = 0;
    int q = merge(tr[p].ch[0], tr[p].ch[1]);
    tr[p].fa = tr[p].ch[0] = tr[p].ch[1] = tr[p].dis = 0;
    return q;
}

int n, m, k, x, y, z, rt[MAXN];
char op;

int main() {
    tr[0].dis = -1;
    n = readint(); m = readint(); k = readint();
    while(k--) {
        op = readop();
        if(op == 'A') {
            x = readint(); y = readint(); z = readint();
            tr[y].val = z;
            rt[x] = merge(y, rt[x]);
        } else if(op == 'D') {
            x = readint(); y = readint(); z = readint();
            if(!tr[y].fa) rt[x] = poprt(y);
            else erase(y);
            tr[y].val -= z;
            rt[x] = merge(rt[x], y);
        } else if(op == 'T') {
            x = readint(); y = readint();
            rt[y] = merge(rt[x], rt[y]);
            rt[x] = 0;
        } else if(op == 'M') {
            x = readint();
            printf("%d\n", tr[rt[x]].val);
        } else {
            x = readint(); y = readint(); int p = rt[x];
            if((tr[p].ch[0] && tr[tr[p].ch[0]].val == tr[p].val)
                || (tr[p].ch[1] && tr[tr[p].ch[1]].val == tr[p].val)) {
                puts("ERROR");
                continue;
            }
            rt[x] = poprt(p);
            tr[p].val += y;
            rt[x] = merge(rt[x], p);
        }
    }
    return 0;
}