GDCPC2015题解 by lby@SYSU_Spirit_Moon

Problem A. Article

题目大意

DRD要在一个编辑器里打出N个字符并保存
对于某一时刻,有两种操作:

  • 保存当前的结果,需要x单位时间
  • 打出一个字符,需要1单位时间,且有p的概率回到上个保存点

问最优策略下,完成任务的用时期望是多少

算法思路

DP,f[i]表示打出前i个字符并保存所需用时的期望
转移方程为f[i] = min{f[j] + g[i-j] + x}
其中g[i]表示连续打出i个字符不出错的用时期望
可以得到递推式g[i] = (g[i-1] + 1) * (1 - p) + (g[i-1] + 1 + g[i]) * p
整理得g[i] = (g[i-1] + 1) / (1 - p)
不难发现,g的增长速率非常快,故转移时只需考察前面很少的状态

时间复杂度: O(kN)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: A.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 100000 + 5;

int n, x;
double p, f[maxn];

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%lf%d", &n, &p, &x);
                p = 1 - p;
                f[0] = 0;
                For(i,1,n) {
                        double g = 1 / p;
                        f[i] = f[i-1] + g + x;
                        for (int j = i - 2; j >= 0; --j) {
                                g = (g + 1) / p;
                                if (g > f[i]) break;
                                f[i] = min(f[i], f[j] + g + x);
                        }
                }
                printf("Case #%d: %.10f\n", ++cas, f[n]);
        }

        return 0;
}

Problem B. Base64

题目大意

输入一个整数k和一个字符串,输出该字符串使用Base64编码k次后的结果

算法思路

模拟,使用bitset可以很方便的实现字符与01串的相互转换

时间复杂度: O(KL)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: B.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 100000 + 5;

int k;
char s[maxn];
char mp[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void trans()
{
        string bit;
        int n = strlen(s);
        rep(i,n) bit += bitset<8>(s[i]).to_string();
        while (bit.length() % 6) bit += '0';
        n = 0;
        for (int i = 0; i < bit.length(); i += 6)
                s[n++] = mp[bitset<6>(bit.substr(i, 6)).to_ulong()];
        while (n % 4) s[n++] = '=';
        s[n] = 0;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%s", &k, s);
                while (k--) trans();
                printf("Case #%d: %s\n", ++cas, s);
        }

        return 0;
}

Problem C. Calculator

题目大意

给出一个长度为N,只包含加、乘、乘方的运算序列

  • 例如:*4, +2, ^3, +8, *6

要求支持两种操作:

  • 输入x,求依次运算后对29393取模的结果
    • 例如 x = 2: ((((24)+2)3)+8)6)=6048
  • 修改序列中某个位置的运算

算法思路

对29393分解质因数得29393 = 7 * 13 * 17 * 19

  • 对于修改,可以使用线段树维护对每个因子取模时的映射,表示某个值经过该区间上的运算所得的值
  • 对于询问,通过整个区间的映射得出对每个因子取模后的值,再通过中国剩余定理合并

具体的线段树维护方式,请参照下面的代码

时间复杂度: O(NlogN+MlogN)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: C.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 50000 + 5;
const int mod[] = {7, 13, 17, 19};

int n, m;
char p[maxn];
int v[maxn], r[4];
int f[1<<18][4][20];

int pow_mod(int a, int b, int m)
{
        int res = 1;
        for (; b; a = a * a % m, b >>= 1) if (b & 1) res = res * a % m;
        return res;
}

void gcd(LL a, LL b, LL &g, LL &x0, LL &y0)
{
        if (!b) g = a, x0 = 1, y0 = 0;
        else gcd(b, a % b, g, y0, x0), y0 -= x0 * (a / b);
}

int china(int r[], const int m[], int n)
{
        LL r0 = r[0], m0 = m[0];
        for (int i = 1; i < n; ++i) {
                LL r1 = r[i], m1 = m[i], g, x0, y0;
                gcd(m0, m1, g, x0, y0);
                if ((r1 - r0) % g) return -1;
                LL r2 = (r1 - r0) / g * x0 % m1, m2 = m0;
                m0 = m0 / g * m1;
                r0 = (r0 + r2 * m2 % m0) % m0;
        }
        return (r0 + m0) % m0;
}

void update(int o, int l, int r, int i)
{
        if (l == r) {
                rep(i,4) rep(j,mod[i]) {
                        if (p[l] == '+') f[o][i][j] = (j + v[l]) % mod[i];
                        if (p[l] == '*') f[o][i][j] = (j * v[l]) % mod[i];
                        if (p[l] == '^') f[o][i][j] = pow_mod(j, v[l], mod[i]);
                }
                return;
        }
        int mid = (l + r) >> 1, lc = o * 2, rc = o * 2 + 1;
        if (i == -1 || i <= mid) update(lc, l, mid, i);
        if (i == -1 || i > mid) update(rc, mid + 1, r, i);
        rep(i,4) rep(j,mod[i]) f[o][i][j] = f[rc][i][f[lc][i][j]];
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%d\n", &n, &m);
                For(i,1,n) p[i] = getchar(), scanf("%d\n", &v[i]);
                update(1, 1, n, -1);
                printf("Case #%d:\n", ++cas);
                while (m--) {
                        int op, x, i;
                        scanf("%d", &op);
                        if (op == 1) {
                                scanf("%d", &x);
                                rep(i,4) r[i] = f[1][i][x%mod[i]];
                                printf("%d\n", china(r, mod, 4));
                        } else {
                                scanf("%d ", &i);
                                p[i] = getchar(), scanf("%d", &v[i]);
                                update(1, 1, n, i);
                        }
                }
        }

        return 0;
}

Problem D. Doom

题目大意

给出一个长度为N的数列,以及M个查询,初始答案为0
对于每个查询

  • 将数列在[l, r]区间上的和累加到答案中,输出答案对p = 9223372034707292160取模后的值
  • 将数列在[l, r]区间上的每个数变成自己的平方

算法思路

p=9223372034707292160=231×3×5×17×257×65536
单独考虑p的每个质因子q,都是 2k+1 形式的素数,而数列中的数又是 x2y 的形式
因为 ab mod p=ab mod φ(p) mod p
所以, x2y mod q=x2y mod φ(q) mod q=x2y mod 2k mod q
随着y的增大(约30次), x2y mod q 将变1,此时 x2y mod p 为一个定值
故可以用线段树维护区间和,每次查询就是简单的线段树区间求和
对于平方操作,维护每个区间是否已经稳定,对于稳定的区间直接返回,否则进入下层继续更新
注意p的值非常大,需要用unsigned long long以及类似快速幂的mul_mod才不会爆

时间复杂度: O(MlogN+30N)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: D.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

typedef unsigned long long ULL;

const int maxn = 100000 + 5;
const ULL mod = 9223372034707292160ULL;

int n, m;
ULL a[maxn];
ULL sum[1<<18];
bool stb[1<<18];

ULL mul_mod(ULL a, ULL b, ULL m = mod)
{
        ULL res = 0;
        for (; b; a = (a + a) % m, b >>= 1) if (b & 1) res = (res + a) % m;
        return res;
}

void build(int o, int l, int r)
{
        if (l == r) {
                stb[o] = false; sum[o] = a[l];
                return;
        }
        int mid = (l + r) >> 1, lc = o * 2, rc = o * 2 + 1;
        build(lc, l, mid); build(rc, mid + 1, r);
        sum[o] = (sum[lc] + sum[rc]) % mod;
        stb[o] = (stb[lc] && stb[rc]);
}

ULL query(int o, int l, int r, int s, int t)
{
        if (s <= l && r <= t) return sum[o];
        int mid = (l + r) >> 1, lc = o * 2, rc = o * 2 + 1;
        ULL res = 0;
        if (s <= mid) res = (res + query(lc, l, mid, s, t)) % mod;
        if (t > mid) res = (res + query(rc, mid + 1, r, s, t)) % mod;
        return res;
}

void update(int o, int l, int r, int s, int t)
{
        if (stb[o]) return;
        if (l == r) {
                a[l] = mul_mod(a[l], a[l]);
                stb[o] = (sum[o] == a[l]);
                sum[o] = a[l];
                return;
        }
        int mid = (l + r) >> 1, lc = o * 2, rc = o * 2 + 1;
        if (s <= mid) update(lc, l, mid, s, t);
        if (t > mid) update(rc, mid + 1, r, s, t);
        sum[o] = (sum[lc] + sum[rc]) % mod;
        stb[o] = (stb[lc] && stb[rc]);
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%d", &n, &m);
                For(i,1,n) scanf("%llu", &a[i]);
                build(1, 1, n);
                printf("Case #%d:\n", ++cas);
                ULL res = 0;
                while (m--) {
                        int l, r;
                        scanf("%d%d", &l, &r);
                        res = (res + query(1, 1, n, l, r)) % mod;
                        printf("%llu\n", res);
                        update(1, 1, n, l, r);
                }
        }

        return 0;
}

Problem E. Exam

题目大意

DRD有N场考试,每场考试的开始时间为 ei ,持续时间为 li
对于每场考试,需要在考前进行 ri 个小时的复习,问DRD能否顺利完成这N场考试
其中,每场考试的复习时间不需要连续,考试进行期间不能复习

算法思路

贪心,先考先复习
按照开始时间对所有考试排序,然后依次进行
对于某场考试前的空余时间,选择最先考的进行复习

时间复杂度: O(NlogN)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: E.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 100000 + 5;

int n;
struct Exam {
        int r, e, l;
        bool operator<(const Exam &ex) const { return e < ex.e; }
} a[maxn];

bool solve()
{
        sort(a, a + n);
        LL clk = 0, now = 0;
        rep(i,n) {
                LL res = a[i].e - clk;
                if (res < 0) return false;
                while (now < n && res) {
                        if (a[now].r <= res) res -= a[now++].r;
                        else a[now].r -= res, res = 0;
                }
                if (now <= i) return false;
                clk = a[i].e + a[i].l;
        }
        return true;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &n);
                rep(i,n) scanf("%d%d%d", &a[i].r, &a[i].e, &a[i].l);
                printf("Case #%d: %s\n", ++cas, solve()? "YES": "NO");
        }

        return 0;
}

Problem F. Friends

题目大意

某个大小为N的集合有9个子集,现在给出它们之间的包含关系(形如A是B的子集)
问这9个子集有多少种可能的情况

算法思路

由于N个元素的包含关系相互独立
故只需要算出对于一个元素的所有方案数X,则 XN 就是答案
其中X = 32可通过考察样例得出,也可以暴力算得

时间复杂度: O(N2)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: F.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxm = 100000 + 5;

int n;
int m, a[maxm];

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &n);
                m = 0; a[m++] = 1;
                while (n--) {
                        rep(i,m) a[i] *= 32;
                        rep(i,m) if (a[i] >= 10) {
                                if (i + 1 == m) a[m++] = a[i] / 10;
                                else a[i+1] += a[i] / 10;
                                a[i] %= 10;
                        }
                }
                printf("Case #%d: ", ++cas);
                for (int i = m - 1; i >= 0; --i) printf("%d", a[i]);
                puts("");
        }

        return 0;
}

Problem G. Game

题目大意

给出一棵N个节点的树,每个节点有一个权值
要求在树上找出K条从根节点到叶子节点的路径,使得被路径覆盖到的节点的权值和最大
输出最大的权值和,其中被多次覆盖的节点只能累加一次

算法思路

贪心,每次找出权值和最大的一条路径,累加后删除该条路径上的点

粗略的证明:

如果初始状态下权值和最大的路径(记作X)不在最终的K条路径中,则找出其中与X重叠最深的路径中的一条,将其替换为X后结果必然增大,故最终的K条路径中必然包含初始权值最大的一条路径。

而上述贪心等价于将每个节点的值,累加到其子树中权值最大的叶子上
这一过程可通过DFS实现,具体的细节可参见下面的代码
最后筛选出所有叶子中权值最大的K个,累加即为答案

时间复杂度: O(N)

代码

/**
 * Copyright (c) 2015 Authors. All rights reserved.
 * 
 * FileName: G.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-05-25
 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 100000 + 5;

int psz;
struct Edge {
        int v;
        Edge *next;
} epl[maxn*2], *e[maxn];

void add_edge(int u, int v)
{
        Edge *i = epl + psz++;
        i->v = v; i->next = e[u]; e[u] = i;
}

int n, k;
LL val[maxn];
int best[maxn];
int sz;
LL vec[maxn];

void dfs(int u, int pa)
{
        int p = -1;
        for (Edge *i = e[u]; i; i = i->next) {
                int v = i->v;
                if (v == pa) continue;
                dfs(v, u);
                if (p == -1 || val[best[v]] > val[p]) p = best[v];
        }
        if (p == -1) best[u] = u;
        else val[p] += val[u], val[u] = -1, best[u] = p;
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                psz = 0;
                memset(e, 0, sizeof(e));
                scanf("%d%d", &n, &k);
                rep(i,n) scanf("%lld", &val[i]);
                rep(i,n-1) {
                        int u, v;
                        scanf("%d%d", &u, &v);
                        --u; --v;
                        add_edge(u, v);
                        add_edge(v, u);
                }
                dfs(0, -1);
                sz = 0;
                rep(i,n) if (~val[i]) vec[sz++] = -val[i];
                nth_element(vec, vec + k, vec + sz);
                LL res = 0;
                rep(i,min(k,sz)) res += -vec[i];
                printf("Case #%d: %lld\n", ++cas, res);
        }

        return 0;
}

Problem H. Homework

题目大意

给出平面上的N个点
对于平面上的任意一点p,要求任何一条通过p且不经过上面N个点的直线,其两侧的点数都不少于 N3
问符合上面要求的点形成的区域的面积是多少

算法思路

半平面交,考察N个点中所有点对形成的有向直线
若直线的右侧刚好有 N31 个点,则该区域内不存在符合要求的点
反之对于任意一个不符合要求的点,也一定在某个这样的区域内
故该直线左侧对应的半平面是答案可能存在的区域
对所有这样的半平面求交,所得多边形的面积即为所求

时间复杂度: O(N2logN+kNlogkN)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: H.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const double eps = 1e-10;
int sgn(double x) { return x < -eps? -1: x > eps; }

typedef complex<double> Point;
typedef complex<double> Vector;
#define X real()
#define Y imag()

double cross(Vector u, Vector v) { return u.X * v.Y - u.Y * v.X; }

struct Line {
        Point p; Vector v; double a;
        Line(Point p = 0, Vector v = 0): p(p), v(v) { a = arg(v); }
};

bool line_inter(Line a, Line b, Point &p)
{
        double c = cross(a.v, b.v);
        if (!sgn(c)) return false;
        p = a.p + a.v * (cross(b.v, a.p - b.p) / c);
        return true;
}

typedef vector<Point> Polygon;

double polygon_area(const Polygon &g)
{
        double s = 0;
        for (int i = 1; i < (int)g.size() - 1; ++i)
                s += cross(g[i] - g[0], g[i+1] - g[0]);
        return fabs(s) / 2.0;
}

int on_left(Point p, Line l) { return sgn(cross(p - l.p, l.v)) < 0; }

bool cmp(Line a, Line b)
{
        return sgn(a.a - b.a)? a.a < b.a: cross(a.v, b.p - a.p) < 0;
}

void halfplane_inter(Line l[], int n, Polygon &g)
{
        vector<Point> q(n);
        int head = 0, tail = 0;
        sort(l, l + n, cmp);
        for (int i = 1; i < n; ++i) {
                if (!sgn(l[i].a - l[i-1].a)) continue;
                while (head < tail && !on_left(q[tail-1], l[i])) --tail;
                while (head < tail && !on_left(q[head], l[i])) ++head;
                l[++tail] = l[i];
                if (head < tail) line_inter(l[tail-1], l[tail], q[tail-1]);
        }
        while (head < tail && !on_left(q[tail-1], l[head])) --tail;
        if (head < tail) line_inter(l[tail], l[head], q[tail]);
        g.assign(q.begin() + head, q.begin() + tail + 1);
}

typedef pair<double, int> Pdi;

const int maxn = 1000 + 5;

int n, k;
Point p[maxn];
Line l[maxn*maxn];
Pdi vec[maxn];

double solve()
{
        int m = 0;
        rep(i,n) {
                int sz = 0;
                rep(j,n) if (j != i)
                        vec[sz++] = Pdi(arg(p[j] - p[i]), j);
                sort(vec, vec + sz);
                int cnt = 0, now = 0;
                rep(j,sz) {
                        int t = vec[j].second;
                        if (now == j) ++cnt, now = (now + 1) % sz;
                        while (on_left(p[vec[now].second],
                                                Line(p[i], p[t] - p[i])))
                                ++cnt, now = (now + 1) % sz;
                        if (cnt-- == k) l[m++] = Line(p[i], p[i] - p[t]);
                }
        }
        Polygon g;
        halfplane_inter(l, m, g);
        return polygon_area(g);
}

int main()
{
        srand((int)time(NULL));

        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &n);
                k = n / 3;
                rep(i,n) {
                        int x, y;
                        scanf("%d%d", &x, &y);
                        p[i] = Point(x, y);
                }
                printf("Case #%d: %.10f\n", ++cas, solve());
        }

        return 0;
}

Problem I. Inverse

题目大意

对于一个长度 n=2k 的序列 an ,给出如下的编码方式

bi=0j<nf((i or j) xor i)aj

其中 f(x) 表示x的二进制形式中1的个数是否为偶数,是则返回1,否则返回0
现在给出编码后的序列 bn ,要求对其解码得到原序列 an

算法思路

考察k阶编码的系数矩阵 Mk

M1=[1]

M2=[1101]

M3=1111010100111001

M4=1111111101010101001100111001100100001111101001011100001101101001

不难发现有
Mk=[Mk1Mk1¬Mk1Mk1]

Mk 的最后一行全为1,故可以通过DFS递归求解
具体为通过运算将B[]在[l, r]上的值,变为由A[]在[l, r]上的值线性组合的结果,再递归下去

时间复杂度: O(N)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: I.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = (1 << 20) + 5;

int k, n;
LL a[maxn], b[maxn];

void dfs(int s, int n)
{
        if (n == 1) {
                a[s] = b[s];
                return;
        }
        int n2 = n / 2;
        LL t = b[s+n-1] - b[s+n2-1];
        rep(i,n2) b[s+i] = (b[s+i] + b[s+n2+i] - t) / 2;
        rep(i,n2) b[s+n2+i] -= b[s+i];
        dfs(s, n2);
        dfs(s + n2, n2);
}

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d", &k);
                n = 1 << k;
                rep(i,n) scanf("%lld", &b[i]);
                dfs(0, n);
                printf("Case #%d:", ++cas);
                rep(i,n) printf(" %lld", a[i]);
                puts("");
        }

        return 0;
}

Problem J. Joyful

题目大意

对一个M × N矩阵进行K次染色,每次随机选取两个格子作为被染色子矩阵的两个角
问最终被染色的格子的个数期望,其中多次被染色的格子只记一次

算法思路

由于期望的线性性质,答案等于每个格子被染色的概率的和
若某个格子在一次操作中被染色的概率为p,则K次操作后被染色的概率为 1(1p)K
而对于一次操作的概率p,可以使用分类讨论或容斥原理计算

时间复杂度: O(N2)

代码

/**
 * Copyright (c) 2015 Authors. All rights reserved.
 * 
 * FileName: J.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-05-25
 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

double sqr(double x) { return x * x; }

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                int n, m, k;
                scanf("%d%d%d", &n, &m, &k);
                double res = 0;
                rep(i,n) rep(j,m) {
                        double p = 0;
                        p += (sqr(i) + sqr(n - i - 1)) * sqr(m);
                        p += sqr(n) * (sqr(j) + sqr(m - j - 1));
                        p -= (sqr(i) + sqr(n - i - 1)) *
                                (sqr(j) + sqr(m - j - 1));
                        p /= sqr(n) * sqr(m);
                        res += 1 - pow(p, k);
                }
                printf("Case #%d: %.0f\n", ++cas, res);
        }

        return 0;
}

Problem K. Kmax

题目大意

给出随机生成的两个序列A[]和B[],找出集合 C={A[i]+B[j] | 1i,jn} 中第k大的元素

算法思路

由于数列是随机生成的,故集合中重复的元素较少,可以通过优先队列求解
先对A[]和B[]排序、去重,将每个A[i]对应B[]的最后一个位置,并将它们的和插入优先队列中
每次找出队列中的最大值,然后删除与之相等的所有值
若被删除的A[i]的对应位置可以向前调整,则将调整后的和重新插入队列中
执行K次或队列为空时结束

时间复杂度: O(KlogN)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: K.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 100000 + 5;

int n, m, k;
int a[maxn], b[maxn], p[maxn];
set<Pii> st;

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%d", &n, &k); m = n;
                rep(i,n) scanf("%d", &a[i]);
                rep(i,m) scanf("%d", &b[i]);
                sort(a, a + n);
                n = unique(a, a + n) - a;
                sort(b, b + m);
                m = unique(b, b + m) - b;
                st.clear();
                rep(i,n) {
                        p[i] = m - 1;
                        st.insert(Pii(a[i] + b[p[i]], i));
                }
                int now;
                while (!st.empty() && k--) {
                        now = st.rbegin()->first;
                        while (!st.empty() && st.rbegin()->first == now) {
                                int i = st.rbegin()->second;
                                st.erase(--st.end());
                                if (p[i]) st.insert(Pii(a[i] + b[--p[i]], i));
                        }
                }
                printf("Case #%d: %d\n", ++cas, k == -1? now: -1);
        }

        return 0;
}

Problem L. Legend

题目大意

卡牌集齐一套“RGB”就能扔一套技能,现给出他的手牌,问最多能扔几套技能

算法思路

统计R、G、B出现的次数,最小值就是答案

时间复杂度: O(N)

代码

/** * Copyright (c) 2015 Authors. All rights reserved. * * FileName: L.cpp * Author: Beiyu Li <[email protected]> * Date: 2015-05-25 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 1000 + 5;

int n;
char s[maxn];

int main()
{
        int T, cas = 0;
        scanf("%d", &T);

        while (T--) {
                scanf("%d%s", &n, s);
                int r = 0, g = 0, b = 0;
                rep(i,n) {
                        if (s[i] == 'R') ++r;
                        if (s[i] == 'G') ++g;
                        if (s[i] == 'B') ++b;
                }
                printf("Case #%d: %d\n", ++cas, min(r, min(g, b)));
        }

        return 0;
}

你可能感兴趣的:(题解,GDCPC2015)