Codeforces Good Bye 2014 部分题解

题面我就不给出了,大家有兴趣可以自己去看一看。。。毕竟我懒。。。


Solution A

这个题直接按照题目所给的条件去做就是了。

雅兴高的可以建图跑 dfs 。

不过由于位置是单调的,

所以我们直接一遍扫过去就可以了。

时间复杂度为 O(n) 。

Code A

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 300000 + 5

int n, k, Next[N];

int main()
{
    scanf("%d%d", &n, &k);
    int u = 1;
    bool ok = 0;
    for (int i = 1; !ok && i < n; i ++)
    {
        scanf("%d", Next + i);
        Next[i] += i;
        u = u == i ? Next[i] : u;
        if (u == k) ok = 1;
    }
    puts(ok ? "YES" : "NO");
    return 0;
}

Solution B

这个题看起来只有 A[i][j] == 1 的才能换。

如果我们把 A[][] 看做邻接矩阵的话,

实际上只要 u 和 v 在同一个联通块里边,

u 就可以和 v 直接交换。

【证明】

假设有一条从 u 到 v 的路径 u - a - ... - b - v

那么我们用 u 一路换到 v,变成这样:

a - ... - b - v - u

然后再用 v 一路换到 a,变成这样:

v - a - ... - b - u。

这就等价于 u 和 v 的直接交换。

好的,因为要求字典序最小的排列,

那么我们就可以给每个联通块里边的元素排个序,

就可以得到字典序最小的排列了。

时间复杂度为 O(n^2)。

Code B

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 300 + 5

int n, A[N], Fa[N], Ord[N];
bool Map[N][N], Flag[N];

inline int Find(int x)
{
    return x == Fa[x] ? x : Fa[x] = Find(Fa[x]);
}

inline bool cmp(int u, int v)
{
    return A[u] < A[v];
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
    {
        scanf("%d", A + i);
        Ord[i] = Fa[i] = i;
    }
    sort(Ord + 1, Ord + n + 1, cmp);
    char ch = '\n';
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
        {
            while (ch != '0' && ch != '1')
                ch = getchar();
            if (ch == '1') Fa[Find(i)] = Find(j);
            ch = getchar();
        }
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            Map[i][j] = Find(i) == Find(j);
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            if (Map[i][Ord[j]] && !Flag[j])
            {
                printf("%d%c", j, i == n ? '\n' : ' ');
                Flag[j] = 1;
                break ;
            }
    
    return 0;
}

Solution C

这个题看起来不那么好弄的样子。

首先,这个题是要找到一个书本的摆放顺序,

使得按照题目所给的操作看书时拿书的总重量最小。

我们可以先考虑这样来确定顺序:

顺序枚举看书顺序表,

如果当前的书还没有被看过,

那么把它放在所有现在已经看完了的书的下方,

否则就不管它。

对于至始至终没有被看过的书,

就按任意顺序放在最下方。

这样子的顺序是最优的。

【伪证明】

首先考虑如果有一本书被看过,

那么这本书之后的位置就随之确定了。

所以我们考虑在某个时刻还没有被看过一次的书。

如果我们能够证明把这本书放下面比放上面优,

那么我们就可以构造一个所有书都满足条件的方案,

于是这个问题就解决了。

如果这本书放在之前要看的书的上面,

那么在拿书的时候,

我们考虑的这本书就要对答案做贡献。

而如果放下面,

则不会产生贡献。

所以之前所说的顺序构造方法是最优的。

(感觉证得好伪。。。)

现在我们得到了这样的书的摆放顺序,

接下来就是直接模拟了。

看一本书所需要的拿的书的数量是 O(n) 的。

要拿 m 本书,

所以我们就直接暴力模拟即可。

反正数据范围小。

时间复杂度 O(nm) 。

Code C

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 1500 + 5

int n, m, ans, A[N], B[N], Ord[N];
bool Flag[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%d", A + i);
    for (int i = 1; i <= m; i ++)
    {
        scanf("%d", B + i);
        if (!Flag[B[i]]) Ord[++ Ord[0]] = B[i];
        Flag[B[i]] = 1;
    }
    for (int i = 1; i <= m; i ++)
    {
        int j = 1;
        for (; Ord[j] != B[i]; j ++) ans += A[Ord[j]];
        for (; j > 1; j --) swap(Ord[j], Ord[j - 1]);
    }
    printf("%d\n", ans);
    
    return 0;
}

Solution D

这个题还是比较漂亮的。

对于链的权值的期望,我们是很难维护的。

因为有 O(n^2) 条链,然后还要带修改。。。

反正我不会。

所以我们可以考虑每一条边对答案的贡献。

首先我们知道,期望是可以加的。

于是我们这么做是可以的。

我们就可以考虑有多少情况下这条边会出现。

设 x 为该边一端的点的个数,另一端则有 (n - x) 个点。

于是有 x * (x - 1) / 2 *(n - x)+ (n - x) * (n - x - 1) / 2 * x 种情况下,

这条边会对答案有贡献。

然后我们注意到一条边要么不被计算,要么就被计算两次。

所以一条边对期望的贡献就是:W * [x * (x - 1) * (n - x) + (n - x) * (n - x - 1) * x] / [n * (n - 1) * (n - 2)]

其中 W 为该边的边权。

于是整个题就轻松愉快了。

可以 O(1) 地处理询问了。

时间复杂度 O(n + q)。

Code D

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef long double LD;
#define N 100000 + 5

int n, tot = 1, Head[N], Size[N], E[N], W[N];
LD ans;
LL A_n_3;

struct Edge
{
    int next, node, w;
}h[N << 1];

inline void addedge(int u, int v, int w)
{
    h[++ tot].next = Head[u], Head[u] = tot;
    h[tot].node = v, h[tot].w = w;
    h[++ tot].next = Head[v], Head[v] = tot;
    h[tot].node = u, h[tot].w = w;
}

inline void dfs(int z, int fa)
{
    Size[z] = 1;
    for (int i = Head[z]; i; i = h[i].next)
    {
        int d = h[i].node;
        if (d == fa) continue ;
        dfs(d, z);
        Size[z] += Size[d];
        W[i >> 1] = Size[d];
    }
}

inline LD Calc(int pos, int delta)
{
    LL t_1 = (LL) W[pos] * (W[pos] - 1) * (n - W[pos]) * 6;
    LL t_2 = (LL) W[pos] * (n - W[pos]) * (n - W[pos] - 1) * 6;
    LD t = (LD) (t_1 + t_2) / A_n_3;
    return t * delta;
}

int main()
{
    scanf("%d", &n);
    A_n_3 = (LL) n * (n - 1) * (n - 2);
    for (int i = 1; i < n; i ++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w);
        E[i] = w;
    }
    dfs(1, 0);
    for (int i = 1; i < n; i ++)
        ans += Calc(i, E[i]);
    int q;
    scanf("%d", &q);
    while (q --)
    {
        int pos, num;
        scanf("%d%d", &pos, &num);
        ans -= Calc(pos, E[pos]);
        ans += Calc(pos, num);
        E[pos] = num;
        printf("%.7lf\n", (double) ans);
    }

    return 0;
}

Solution E

如果我们可以枚举 i 并维护左端点为 1 .. i ,右端点 i 的答案。

于是这个题就可以离线做了。

事实上也是如此。

我们可以把一根那啥变成一条线段 [L, L + H],

于是就问题就变成了求 (l, r) 中的线段的空白部分大小。

空白就意味着前一根那啥碰不到后一根那啥。

于是我们就可以根据线段的右端点位置维护一个单调递减栈。

这意味着栈中两个相邻元素之间的那啥都可以统一考虑。

然后对于两个相邻元素之间的那啥都可以统一处理。

我们注意到:

如果某个决策区间的那啥够不到当前枚举的那啥,

那么我们将对这个区间的答案进行区间增量,

然后这个区间就会被删去。

所以:

每个那啥所代表的区间只会进行 O(1) 次操作。

于是这个题再弄个线段树来搞区间增量,单点询问,

时间复杂度就是 O(n log n) 的了。

啊。。。感觉我并没有讲清。。。

那就看看我的代码吧。。。TAT

Code E

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
#define N 200000 + 5
#define M 600000 + 5
#define ls(x) x << 1
#define rs(x) x << 1 | 1

int n, T, size, L[N], R[N], q[N];
LL Ans[N];

struct Querys
{
    int l, r, id;
    Querys (int _l = 0, int _r = 0, int _id = 0) {l = _l, r = _r, id = _id;}
    bool operator < (const Querys a) const
    {
        return r < a.r;
    }
}Ask[N];

struct Segment_Tree
{
    LL num, delta;
}h[M];

inline void apply(int x, int d)
{
    h[x].num += d;
    h[x].delta += d;
}

inline void push(int x)
{
    if (h[x].delta)
    {
        apply(ls(x), h[x].delta);
        apply(rs(x), h[x].delta);
        h[x].delta = 0;
    }
}

inline void Modify(int x, int l, int r, int s, int t, int d)
{
    if (l == s && r == t)
    {
        apply(x, d);
        return ;
    }
    push(x);
    int mid = l + r >> 1;
    if (t <= mid)
        Modify(ls(x), l, mid, s, t, d);
    else if (s > mid)
        Modify(rs(x), mid + 1, r, s, t, d);
    else Modify(ls(x), l, mid, s, mid, d), Modify(rs(x), mid + 1, r, mid + 1, t, d);
}

inline LL Query(int x, int l, int r, int t)
{
    if (l == r)
        return h[x].num;
    push(x);
    int mid = l + r >> 1;
    if (t <= mid)
        return Query(ls(x), l, mid, t);
    else return Query(rs(x), mid + 1, r, t);
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
    {
        scanf("%d%d", L + i, R + i);
        R[i] += L[i];
    }
    scanf("%d", &T);
    for (int i = 1; i <= T; i ++)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        Ask[i] = Querys(l, r, i);
    }
    sort(Ask + 1, Ask + T + 1);
    
    int t = 1;
    for (int i = 1; i <= n; i ++)
    {
        for (; R[q[size]] <= R[i] && size; size --)
            if (R[q[size]] < L[i])
                Modify(1, 1, n, q[size - 1] + 1, q[size], L[i] - R[q[size]]);
        q[++ size] = i;
        while (Ask[t].r == i && t <= T)
        {
            Ans[Ask[t].id] = Query(1, 1, n, Ask[t].l);
            t ++;
        }
    }
    for (int i = 1; i <= T; i ++)
        printf("%I64d\n", Ans[i]);
    
    return 0;
}

Solution F

这个题想必是分治吧。

我们可以弄一个 Solve(l, r),

表示处理时间点在 [l, r] 中的询问。

首先,如果某个 item 在 [l, r] 这整个时间区间内都是可以被买的,

那么就把这个物品 Dp 一下,

也就是 01 背包了。

然后给这个物品打上“已经被处理”的标记。

然后再递归进行 Solve(l, mid) 和 Solve(mid + 1, r)。

直到 l == r 的时候记录询问时间点正好为 l 的答案。

一定记得要回溯!

然后最重要的一点:

一个物品只会被处理 O(log n) 次。

类似于每一条线段都对应着线段树的 O(log n) 个区间, 

所以时间复杂度为 O(nT + nB log n)

T 为时间的最大值,B 为 预算金额的最大值。

Code F

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 4000 + 5
#define M 20000 + 5
#define INF 0x7fffffff

int n, p, q, Max, _Max, C[N], W[N], A[N], Dp[21][N], Max_Dp[21][N], T[M], B[M], Ans[M], Done[N];
vector <int> Map[M];

inline void Solve(int depth, int l, int r)
{
	for (int i = 1; i <= n; i ++)
		if (!Done[i] && A[i] <= l && A[i] + p > r)
		{
			Done[i] = depth;
			for (int j = _Max; j >= C[i]; j --)
				Dp[depth][j] = max(Dp[depth][j], Dp[depth][j - C[i]] + W[i]);
		}
	for (int i = 0; i <= _Max; i ++)
		Max_Dp[depth][i] = i == 0 ? Dp[depth][i] : max(Dp[depth][i], Max_Dp[depth][i - 1]);
	if (l == r)
	{
		for (int i = 0; i < Map[l].size(); i ++)
			Ans[Map[l][i]] = Max_Dp[depth][B[Map[l][i]]];
		for (int i = 1; i <= n; i ++)
			if (Done[i] == depth) Done[i] = 0;
		return ;
	}
	int mid = l + r >> 1;
	for (int i = 0; i <= _Max; i ++)
		Dp[depth + 1][i] = Dp[depth][i];
	Solve(depth + 1, l, mid);
	for (int i = 0; i <= _Max; i ++)
		Dp[depth + 1][i] = Dp[depth][i];
	Solve(depth + 1, mid + 1, r);
	for (int i = 1; i <= n; i ++)
		if (Done[i] == depth) Done[i] = 0;
}

int main()
{
	scanf("%d%d", &n, &p);
	for (int i = 1; i <= n; i ++)
		scanf("%d%d%d", C + i, W + i, A + i);
	scanf("%d", &q);
	for (int i = 1; i <= q; i ++)
	{
		scanf("%d%d", T + i, B + i);
		Max = max(Max, T[i]);
		_Max = max(_Max, B[i]);
		Map[T[i]].push_back(i);
	}
	for (int i = 1; i < _Max; i ++)
		Dp[0][i] = -INF;
	Solve(0, 1, Max);
	for (int i = 1; i <= q; i ++)
		printf("%d\n", Ans[i]);
	
	return 0;
}

至于 G 题,我太弱不会。于是等我会了的时候再来更新吧。

所以这只能说是部分题解。。。TAT。。。=_=


你可能感兴趣的:(codeforces,2014,Good,bye)