[题解]NOIP2015提高组の题解集合 - by xyz32768

Day 1

T1 magic

模拟题。不断地往右上或往下走,注意边界。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 55;
int n, a[N][N];
int main() {
    //freopen("magic.in", "r", stdin);
    //freopen("magic.out", "w", stdout);
    int i, j, x = 1, y; n = read(); y = n + 1 >> 1;
    for (i = 1; i <= n * n; i++) {
        a[x][y] = i;
        if (i % n == 0) x++;
        else x--, y++;
        x = (x + n) % n; if (!x) x = n;
        y = (y + n) % n; if (!y) y = n;
    }
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) printf("%d ", a[i][j]);
        printf("\n");
    }
    return 0;
}

T2 message

把每个 i Ti连边,求该有向图的最小环。
有很多种做法。其中一种是,从一个点 u 开始遍历,不断地往Tu走,如果走到了这一次已经遍历过的节点,那么就找到了一个环。注意细节。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
int n, t[N], ans = INF, vis[N], times;
void go(int x) {
    int tmp = times; while (1) {
        int v = t[x];
        if (vis[x] && vis[x] <= tmp) return;
        if (vis[v] > tmp)
            return (void) (ans = min(ans, times - vis[v] + 2));
        vis[x] = ++times; x = v;
    }
}
int main() {
    //freopen("message.in", "r", stdin);
    //freopen("message.out", "w", stdout);
    int i; n = read();
    for (i = 1; i <= n; i++) t[i] = read();
    for (i = 1; i <= n; i++) if (!vis[i]) go(i);
    cout << ans << endl;
    return 0;
}

T3 landlords

不考虑顺子,那么一次打多张一定是最优的。所以可以用贪心求解这一部分。对于顺子,就爆搜。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 25, INF = 0x3f3f3f3f;
int n, cnt[N], typ[] = {0, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
Ans, tmp[N];
int cc() {
    int i, CN = 0; memset(tmp, 0, sizeof(tmp));
    for (i = 0; i <= 13; i++) tmp[cnt[i]]++;
    while (tmp[4] >= 1 && tmp[2] >= 2) tmp[4]--, tmp[2] -= 2, CN++;
    while (tmp[4] >= 1 && tmp[1] >= 2) tmp[4]--, tmp[1] -= 2, CN++;
    while (tmp[4] >= 1 && tmp[2] >= 1) tmp[4]--, tmp[2]--, CN++;
    while (tmp[3] >= 1 && tmp[2] >= 1) tmp[3]--, tmp[2]--, CN++;
    while (tmp[3] >= 1 && tmp[1] >= 1) tmp[3]--, tmp[1]--, CN++;
    return CN + tmp[1] + tmp[2] + tmp[3] + tmp[4];
}
void dfs(int dep) {
    if (dep >= Ans) return; int i, j, k;
    Ans = min(Ans, dep + cc());
    for (i = 2; i <= 13; i++) for (j = 1; j <= 3; j++) {
        if (cnt[i] < j) continue; int tt = 0;
        for (k = i; cnt[k] >= j; k++) tt += j, cnt[k] -= j;
        for (; (--k) >= i;) {
            if (tt >= 5) dfs(dep + 1);
            tt -= j, cnt[k] += j;
        }
    }
}
void work() {
    int i; memset(cnt, 0, sizeof(cnt));
    for (i = 1; i <= n; i++) cnt[typ[read()]]++, read();
    Ans = INF; dfs(0); printf("%d\n", Ans);
}
int main() {
    //freopen("landlords.in", "r", stdin);
    //freopen("landlords.out", "w", stdout);
    int T = read(); n = read();
    while (T--) work();
    return 0;
}

Day 2

T1 stone

基础的二分题。二分最短跳跃距离后判定是否可能。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 5e4 + 5;
int L, n, m, a[N], minv;
bool check(int mid) {
    int i, tot = 0, pre = 0;
    for (i = 1; i <= n + 1; i++)
        if (a[i] - pre < mid) tot++;
        else pre = a[i];
    return tot <= m;
}
int solve() {
    int l = minv, r = 1e9, mid;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid)) l = mid + 1;
        else r = mid - 1;
    }
    return r;
}
int main() {
    //freopen("stone.in", "r", stdin);
    //freopen("stone.out", "w", stdout);
    int i; L = read(); n = read(); m = read();
    for (i = 1; i <= n; i++) a[i] = read(),
        minv = min(minv, a[i]); a[n + 1] = L;
    printf("%d\n", solve());
    return 0;
}

T2 substring

考虑DP。
定义状态 f[i][j][k][0/1] 表示字符串 A 的前i个字符和字符串 B 的前j个字符用了 k 个子串,第四维为1表示 A 字符串的第i个字符必须被选出,为 0 表示A字符串的第 i 个字符不能被选出。
那么很容易得出转移:
1、对于任意一个0in f[i][0][0][0]=1
2、 f[i][j][k][0]=f[i1][j][k][0]+f[i1][j][k][1]
3、如果 A[i]=B[j] ,那么 f[i][j][k][1]=f[i1][j1][k][1]+f[i1][j1][k1][0]+f[i1][j1][k1][1]
最后结果即为 f[n][m][K][0]+f[n][m][K][1]
考虑到空间问题,滚动 f 的第一维以节省空间。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1005, M = 205, PYZ = 1e9 + 7;
int n, m, K, f[2][M][M][2];
char a[N], b[M];
int main() {
    //freopen("substring.in", "r", stdin);
    //freopen("substring.out", "w", stdout);
    int i, j, k; n = read(); m = read(); K = read();
    scanf("%s", a + 1); scanf("%s", b + 1); f[0][0][0][0] = 1;
    for (i = 1; i <= n; i++) {
        int op = i & 1; f[op][0][0][0] = 1;
        for (j = 1; j <= min(i, m); j++) for (k = 1; k <= min(j, K); k++) {
            f[op][j][k][0] = f[op][j][k][1] = 0;
            f[op][j][k][0] = (f[op ^ 1][j][k][0] + f[op ^ 1][j][k][1]) % PYZ;
            if (a[i] == b[j]) {
                f[op][j][k][1] = (f[op ^ 1][j - 1][k][1] +
                    f[op ^ 1][j - 1][k - 1][0]) % PYZ;
                (f[op][j][k][1] += f[op ^ 1][j - 1][k - 1][1]) %= PYZ;
            }
        }
    }
    printf("%d\n", (f[n & 1][m][K][0] + f[n & 1][m][K][1]) % PYZ);
    return 0;
}

T3 transport

首先存下每个运输计划的起点st[i],终点 ed[i] ,最近公共祖先 lca[i] 以及耗时 len[i]
先二分最短时间 time
在判定中,先标记出所有 len[i]>time 的运输计划,虫洞必须位于这些运输计划的路径的公共边上。
这里,为每个点赋予一个权值,初始为 0
然后对于任意一个len[i]>time的运输计划,把 st[i] ed[i] 的路径上的所有点( lca[i] 除外)的点权,全部加上 1
这样容易知道,最后如果一个点的点权等于所有len[i]>time的运输计划的个数,那么连接这个点和这个点的父亲的边被改造成虫洞后会降低所有 len[i]>time 的运输计划的耗时,降低量为这条边的边权。所以,在符合条件的边中,选取一条边权最大的边,判断是否:耗时最长的计划消耗的时间 选取的边的边权time
对于路径加 1 的操作,可以使用树剖实现,但是常数较大。但是可以发现这里的询问都是单点询问,而且都在路径修改之后。对于这一点,可以使用差分数组代替树剖,将复杂度去掉一个log
建立差分数组 b[u]=val[u]val[v] ,其中 val[u] u 的点权,v u 的子节点。
对于u v 的路径加x(不含 lca )的操作为:
b[u]+=x,b[v]+=x,b[lca]=2x
而单点询问就是求子树和。最后DFS一遍就可以求出所有点的点权。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 3e5 + 5, LogN = 23;
int n, m, a[N], ecnt, nxt[N << 1], adj[N], go[N << 1], fa[N][LogN],
dep[N], T[N], val[N << 1], dis[N], len[N], st[N], ed[N],
maxv, _lca[N], top[N], v_res, cnt_now;
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt;
    go[ecnt] = v; val[ecnt] = w;
}
void dfs(int u, int fu) {
    int i; dep[u] = dep[fu] + 1; for (i = 1; i <= 21; i++)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for (int e = adj[u], v; e; e = nxt[e]) {
        if ((v = go[e]) == fu) continue;
        fa[v][0] = u; dis[v] = dis[u] + val[e];
        top[v] = val[e]; dfs(v, u);
    }
}
int lca(int u, int v) {
    int i; if (dep[u] < dep[v]) swap(u, v);
    for (i = 21; i >= 0; i--) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    for (i = 21; i >= 0; i--) if (fa[u][i] != fa[v][i])
        u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
int dist(int i, int u, int v) {
    return dis[u] + dis[v] - (dis[_lca[i] = lca(u, v)] << 1);
}
void change(int i, int x) {
    T[st[i]] += x; T[ed[i]] += x;
    T[_lca[i]] -= x << 1;
}
int dfs_ans(int u, int fu) {
    int res_d = T[u];
    for (int e = adj[u], v; e; e = nxt[e]) {
        if ((v = go[e]) == fu) continue;
        res_d += dfs_ans(v, u);
    }
    if (res_d == cnt_now) v_res = max(v_res, top[u]);
    return res_d;
}
bool check(int mid) {
    int i, res = 0; for (i = 0; i <= n; i++) T[i] = 0; cnt_now = 0;
    for (i = 1; i <= m; i++) if (len[i] > mid) cnt_now++, change(i, 1);
    if (!cnt_now) return 1; v_res = 0; dfs_ans(1, 0);
    return maxv - v_res <= mid;
}
int solve() {
    int l = 0, r = maxv, mid;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid)) r = mid - 1;
        else l = mid + 1;
    }
    return l;
}
int main() {
    //freopen("transport.in", "r", stdin);
    //freopen("transport.out", "w", stdout);
    int i, x, y, z; n = read(); m = read();
    for (i = 1; i < n; i++) {
        x = read(); y = read(); z = read();
        add_edge(x, y, z); add_edge(y, x, z);
    }
    dfs(1, 0);
    for (i = 1; i <= m; i++) {
        st[i] = read(); ed[i] = read();
        len[i] = dist(i, st[i], ed[i]);
        maxv = max(maxv, len[i]);
    }
    printf("%d\n", solve());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值