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

本文总结了三天编程竞赛中的关键算法和技巧,包括模拟题解法、图论问题处理及高精度计算优化等,通过具体题目解析,展示了如何高效解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Day 1

T1 rps

模拟题。根据(i1)modNA+1(i1)modNB+1的值判断这一次的胜负来统计答案。
代码:

#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 = 205;
int ac[5][5] = {
    {0, -1, 1, 1, -1},
    {1, 0, -1, 1, -1},
    {-1, 1, 0, -1, 1},
    {-1, -1, 1, 0, 1},
    {1, 1, -1, -1, 0}
}, n, na, nb, cnt1, cnt2, a[N], b[N];
int main() {
    //freopen("rps.in", "r", stdin);
    //freopen("rps.out", "w", stdout);
    int i; n = read(); na = read(); nb = read();
    for (i = 1; i <= na; i++) a[i] = read();
    for (i = 1; i <= nb; i++) b[i] = read();
    for (i = 1; i <= n; i++) {
        int x = a[(i - 1) % na + 1], y = b[(i - 1) % nb + 1];
        if (ac[x][y] == 1) cnt1++;
        else if (ac[x][y] == -1) cnt2++;
    }
    printf("%d %d\n", cnt1, cnt2);
    return 0;
}

考虑到uv距离为2的两种可能:
1、uv的爷爷或vu的爷爷。
2、uv的父亲相同。
对于情况1,由于一个节点最多只有一个爷爷,所以情况1简单统计(由于是有序点对,所以要乘2)。
对于情况2,先枚举uv的父亲fa
对于寻找最大值,找出fa的权值最大的两个子节点,用它们权值的积更新答案。
对于求和,先不考虑是否选了两个相同的子节点。
sumfa的子节点权值和,那么如果固定一个子节点u,那么所有的子节点都可以充当v,所以此时对答案的贡献为Wusum。这样总和就为sum2
然后从sum2中去除掉选了同一个子节点的情况,就可以计入答案了。
注意:1、最大值不要取模。2、合法点对(u,v)(v,u)是两个不同的点对。
代码:

#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;
}
typedef long long ll;
const int N = 2e5 + 5, PYZ = 10007;
int n, tot, a[N], w[N], ecnt, nxt[N << 1], adj[N], go[N << 1], sum;
ll mv;
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
bool comp(int x, int y) {
    return w[x] < w[y];
}
void dfs(int u, int fu) {
    int i; tot = 0; ll res = 0, tmp = 0;
    for (int e = adj[u], v; e; e = nxt[e])
        if ((v = go[e]) != fu) a[++tot] = v, res += w[v],
            tmp += 1ll * w[v] * w[v];
    sort(a + 1, a + tot + 1, comp); ll tm = sum;
    (sum += ((res % PYZ) * (res % PYZ) - tmp % PYZ) % PYZ + PYZ) %= PYZ;
    if (tot >= 2) mv = max(mv, 1ll * w[a[tot - 1]] * w[a[tot]]);
    if (fu) {
        mv = max(mv, 1ll * w[fu] * w[a[tot]]);
        (sum += 2ll * w[fu] * res % PYZ) %= PYZ;
    }
    for (int e = adj[u], v; e; e = nxt[e])
        if ((v = go[e]) != fu) dfs(v, u);
}
int main() {
    //freopen("link.in", "r", stdin);
    //freopen("link.out", "w", stdout);
    int i, x, y; n = read();
    for (i = 1; i < n; i++) x = read(), y = read(), add_edge(x, y);
    for (i = 1; i <= n; i++) w[i] = read();
    dfs(1, 0); cout << mv << " " << sum << endl;
    return 0;
}

T3 bird

完全背包DP。设f[i][j]为到了位置(i,j)的最少点击次数,如果无法到达则为。一开始先把f的所有数初始化为(以下把所有的f[i][0]都规定为)。
边界为:
0<jm,f[0][j]=0
转移:
1、当j=m时,
0k<m,f[i][j]=min(f[i][j],f[i1][k]+jk1Xi1+1)
f[i][j]=min(f[i][j],f[i1][m]+1)
(即当高度达到m时继续操作则保持高度)
2、当jXi1时,
如果jXi12,则
f[i][j]=min(f[i][j],min(f[i1][jXi1]+1,f[i][jXi1]+1))
否则f[i][j]=min(f[i][j],f[i1][jXi1]+1)
(即此操作上升)
3、当jmYi1时,
f[i][j]=min(f[i][j],f[i1][j+Yi1])
(即此操作下降)
注意一些细节。
代码:

#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 = 1e4 + 5, M = 1005, INF = 0x3f3f3f3f;
int n, m, K, X[N], Y[N], U[N], D[N], f[N][M];
bool is[N];
void chkmin(int &a, int b) {a = min(a, b);}
int main() {
    //freopen("bird.in", "r", stdin);
    //freopen("bird.out", "w", stdout);
    int i, j, k, x, cnt = 0; n = read(); m = read(); K = read(); U[n] = m + 1;
    for (i = 0; i < n; i++) X[i] = read(), Y[i] = read(), U[i] = m + 1;
    for (i = 1; i <= K; i++) x = read(), D[x] = read(), U[x] = read(),
        is[x] = 1;
    for (i = 1; i <= n; i++) for (j = 0; j <= m + 1; j++) f[i][j] = INF;
    for (i = 1; i <= n; i++) {
        for (j = 0; j <= m; j++) {
            if (j == m) {
                for (k = 0; k <= m; k++)
                    chkmin(f[i][j], f[i - 1][k] + (
                        k == m ? 1 : (j - k - 1) / X[i - 1] + 1));
                continue;
            }
            if (j >= X[i - 1]) {
                if (j >= (X[i - 1] << 1)) chkmin(f[i][j],
                    min(f[i - 1][j - X[i - 1]] + 1, f[i][j - X[i - 1]] + 1));
                else chkmin(f[i][j], f[i - 1][j - X[i - 1]] + 1);
            }
        }
        for (j = 0; j <= m - Y[i - 1]; j++)
            chkmin(f[i][j], f[i - 1][j + Y[i - 1]]);
        for (j = 0; j <= D[i]; j++) f[i][j] = INF;
        for (j = U[i]; j <= m + 1; j++) f[i][j] = INF;
        if (is[i]) {
            bool flag = 0;
            for (j = D[i] + 1; j <= U[i] - 1; j++)
                if (f[i][j] < INF) {flag = 1; break;}
            if (flag) cnt++;
        }
    }
    int res = INF;
    for (j = 1; j <= m; j++) chkmin(res, f[n][j]);
    if (res < INF) printf("1\n%d\n", res);
    else printf("0\n%d\n", cnt);
    return 0;
}

Day 2

T1 wireless

模拟题,暴力枚举每个区域即可。注意边界。
代码:

#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 = 134;
int d, n, a[N][N];
int check(int x, int y) {
    int i, j, x1, y1, x2, y2, ans = 0;
    x1 = max(0, x - d); y1 = max(0, y - d);
    x2 = min(128, x + d); y2 = min(128, y + d);
    for (i = x1; i <= x2; i++) for (j = y1; j <= y2; j++)
        ans += a[i][j]; return ans;
}
int main() {
    //freopen("wireless.in", "r", stdin);
    //freopen("wireless.out", "w", stdout);
    int i, j, tot = 0, ans = 0; d = read(); n = read();
    for (i = 1; i <= n; i++) a[read()][read()] = read();
    for (i = 0; i <= 128; i++) for (j = 0; j <= 128; j++)
        ans = max(ans, check(i, j));
    for (i = 0; i <= 128; i++) for (j = 0; j <= 128; j++)
        tot += (check(i, j) == ans);
    printf("%d %d\n", tot, ans);
    return 0;
}

T2 road

构造出一个原图和一个各边方向相反的反图。先在反图上从终点开始进行一遍DFS,标记出可以到达终点的点,再标记出路径上不能包含(即存在一个与其相邻的节点不能到达终点)的节点,然后SPFA即可。
代码:

#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 = 1e4 + 5, M = 2e5 + 5, INF = 0x3f3f3f3f;
int n, m, ecnt, nxt[M], adj[N], go[M], ecnt2, nxt2[M], adj2[N], go2[M],
que[M << 2], len, dis[N], S, T; bool vis[N], con[N], can[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt2[++ecnt2] = adj2[v]; adj2[v] = ecnt2; go2[ecnt2] = u;
}
void dfs(int u) {
    can[u] = 1;
    for (int e = adj2[u], v; e; e = nxt2[e])
        if (!can[v = go2[e]]) dfs(v);
}
void mark() {
    memset(con, true, sizeof(con));
    int i; for (i = 1; i <= n; i++) for (int e = adj[i]; e; e = nxt[e])
        if (!can[go[e]]) {con[i] = 0; break;}
}
int spfa() {
    if (!con[S]) return -1;
    int i; memset(dis, INF, sizeof(dis));
    dis[que[len = 1] = S] = 0;
    for (i = 1; i <= len; i++) {
        int u = que[i]; vis[u] = 0;
        for (int e = adj[u], v; e; e = nxt[e])
            if (con[v = go[e]] && dis[u] + 1 < dis[v]) {
                dis[v] = dis[u] + 1;
                if (!vis[v]) vis[que[++len] = v] = 1;
            }
    }
    return dis[T] < INF ? dis[T] : -1;
}
int main() {
    //freopen("road.in", "r", stdin);
    //freopen("road.out", "w", stdout);
    int i, x, y; n = read(); m = read();
    for (i = 1; i <= m; i++) x = read(), y = read(),
        add_edge(x, y); S = read(); T = read();
    printf("%d\n", (dfs(T), mark(), spfa()));
    return 0;
}

T3 equation

首先,考虑怎样解决高精度计算的复杂度问题。
可以想到取模。选pp为常数,约在510之间)个质数(约在104105之间),对每个系数分别取模,对于判断一个解是否合法,即判断在这p个模数的意义下,原式的值是否都为0。这样就得到了O(nmp)的算法。
考虑继续优化。可以发现,对于任意整数a,x,在模b意义下,自变量为xab+x时原式的值是同余的。所以只要对于每一个模数bi,预处理出自变量从0bi1时原式的值。这样枚举解时,只要求出了该自变量取模每个质数的结果,就可以得出解了。
复杂度O(nb+mp)b为选取的质数之和。
代码:

#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 = 105, M = 1e4 + 5, MOD = 5e4 + 5;
int PYZ[] = {10007, 25933, 27367, 38501, 41177}, n, m, val[N][7],
sol[7][MOD];
char s[M];
int ModPYZ(int len, int pyz) {
    int i = 1 + (s[1] == '-'), ans = 0;
    for (; i <= len; i++) ans = (ans * 10 + s[i] - '0') % pyz;
    if (s[1] == '-') ans = pyz - ans; if (ans == pyz) ans = 0;
    return ans;
}
int solve(int x, int id) {
    int i, now = 1, ans = 0;
    for (i = 0; i <= n; i++, (now *= x) %= PYZ[id])
        (ans += val[i][id] * now) %= PYZ[id];
    return ans;
}
int main() {
    //freopen("equation.in", "r", stdin);
    //freopen("equation.out", "w", stdout);
    int i, j, l, cnt = 0; n = read(); m = read();
    for (i = 0; i <= n; i++) {
        scanf("%s", s + 1); l = strlen(s + 1);
        for (j = 0; j < 5; j++) val[i][j] = ModPYZ(l, PYZ[j]);
    }
    for (i = 0; i < 5; i++) for (j = 0; j < PYZ[i]; j++)
        sol[i][j] = solve(j, i);
    for (i = 1; i <= m; i++) {
        bool flag = 1; for (j = 0; j < 5; j++)
            flag = flag && (sol[j][i % PYZ[j]] == 0);
        if (flag) cnt++;
    }
    printf("%d\n", cnt);
    for (i = 1; i <= m; i++) {
        bool flag = 1; for (j = 0; j < 5; j++)
            flag = flag && (sol[j][i % PYZ[j]] == 0);
        if (flag) printf("%d\n", i);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值