2016弱校连萌(一)

本文精选了几道典型的算法竞赛题目,包括寻找三维立方体内或表面上的最近点、求解不同硬币组合的数量、计算无向图中两点间的最短路径等,并提供了详细的解题思路与代码实现。

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

记录一下
A
题意:给定三维空间里一个立方体,让你在立方体表面或者内部找到一个点 T 使得与点P距离最近。
思路:一个开头向上的二次函数,肯定有最小值。

E
题意:面值为 123 的硬币分别有 a1a2a3 个,问你可以得到多少个不同价值。
思路:分类讨论,注意 a1==0 的情况。

F
题意:给定一个无向连通图, Q 次查询,每次查询问你u>v至少要经过多少条边。
思路:因为边数 — 点数 <=200 ,这样的话我们先用并查集找到一棵生成树,这样只剩下 <=200 非树边。
u>v 的最优解要么全在树上,要么借助非树边。
对于非树边每一个节点,跑一发 BFS 来预处理最短路,最多跑 400 次。
在树边的情况,就是 LCA 的经典问题了,反之枚举中间点即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 200;
const int MOD = 1e9 + 7;
void add(LL &x, LL y) {
    x += y;
    if(x >= MOD) x -= MOD;
}
int father[MAXN];
int Find(int p) {
    int t, son = p;
    while(p != father[p]) { p = father[p]; }
    while(son != p) { t = father[son]; father[son] = p; son = t; }
    return p;
}
struct Edge {
    int from, to, next;
};
Edge edge[MAXN * 2];
int head[MAXN], edgenum;
void init() { memset(head, -1, sizeof(head)); edgenum = 0; }
void addEdge(int u, int v) {
    Edge E = {u, v, head[u]};
    edge[edgenum] = E;
    head[u] = edgenum++;
}
bool vis[MAXN];
int node[MAXN], top;
int dist[300][MAXN];
int n, m, q;
void BFS(int s) {
    for(int i = 1; i <= n; i++) {
        vis[i] = false;
    }
    queue<int> Q;
    vis[node[s]] = true; dist[s][node[s]] = 0; Q.push(node[s]);
    while(!Q.empty()) {
        int u = Q.front(); Q.pop();
        for(int i = head[u]; i != -1; i = edge[i].next) {
            int v = edge[i].to;
            if(!vis[v]) {
                dist[s][v] = dist[s][u] + 1;
                vis[v] = true;
                Q.push(v);
            }
        }
    }
}

vector<int> G[MAXN];
int vs[MAXN << 1], depth[MAXN << 1], id[MAXN];
int dfs_clock;
int dp[MAXN << 1][30];
int D[MAXN];
void DFS(int u, int fa, int d) {
    id[u] = dfs_clock; vs[dfs_clock] = u;
    depth[dfs_clock++] = d;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v == fa) continue;
        D[v] = D[u] + 1;
        DFS(v, u, d + 1);
        vs[dfs_clock] = u;
        depth[dfs_clock++] = d;
    }
}
void find_depth() {
    dfs_clock = 1;
    memset(vs, 0, sizeof(vs));
    memset(id, 0, sizeof(id));
    memset(depth, 0, sizeof(depth));
    memset(D, 0, sizeof(D));
    DFS(1, -1, 0);
}
void RMQ_init(int N) {
    for(int i = 1; i <= N; i++) {
        dp[i][0] = i;
    }
    for(int j = 1; (1 << j) <= N; j++) {
        for(int i = 1; i + (1 << j) - 1 <= N; i++) {
            int a = dp[i][j - 1];
            int b = dp[i + (1 << (j - 1))][j - 1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}
int Query(int L, int R) {
    int k = 0;
    while((1 << (k + 1)) <= R - L + 1) k++;
    int a = dp[L][k];
    int b = dp[R - (1 << k) + 1][k];
    return depth[a] < depth[b] ? a : b;
}
int LCA(int u, int v) {
    int x = id[u];
    int y = id[v];
    return x < y ? vs[Query(x, y)] : vs[Query(y, x)];
}
int main()
{
    while(scanf("%d%d%d", &n, &m, &q) != EOF) {
        init(); top = 0;
        for(int i = 1; i <= n; i++) {
            father[i] = i;
            G[i].clear();
        }
        for(int i = 1; i <= m; i++) {
            int u, v; scanf("%d%d", &u, &v);
            addEdge(u, v); addEdge(v, u);
            int fu = Find(u);
            int fv = Find(v);
            if(fu != fv) {
                father[fu] = fv;
                G[u].push_back(v);
                G[v].push_back(u);
            }
            else {
                node[top++] = u;
                node[top++] = v;
            }
        }
        find_depth(); RMQ_init(dfs_clock - 1);
        sort(node, node + top);
        top = unique(node, node + top) - node;
        for(int i = 0; i < top; i++) {
            BFS(i);
        }
        while(q--) {
            int u, v; scanf("%d%d", &u, &v);
            int ans = D[u] + D[v] - 2 * D[LCA(u, v)];
            for(int i = 0; i < top; i++) {
                ans = min(ans, dist[i][u] + dist[i][v]);
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}

H
题意:给定一个无向连通图,每两个节点之间都有 2c[i] 条不同的边相连。问你从节点 1 出发经过所有边一次(且只有一次)再回到节点1的方案数。
思路: dp[i] 表示从节点 i 出发的方案数。
一、首先考虑i的孩子节点的组合数
假设有 N 个孩子,第j个孩子与 i son[j]条边相连。
则共有回路 num[j]=son[j]2 ,组成这些回路的边都不相同的。
这样方案数是 (Nj=1num[j])!Nj=1(num[j]!)Nj=1(son[j]!)
二、考虑第 j 个孩子如何走完它的子树,可能没有走完j的子树就先回到父亲节点,之后再回来……
j 个孩子与父亲有num[j]个回路,它与它的孩子有 cnt[j] 个回路,这样的话相当于 cnt[j] 个苹果放进 num[j] 个盒子里面。
方案数 Cnum[j]1cnt[j]+num[j]1

dp[i]=(Nj=1num[j])!Nj=1(num[j]!)Nj=1(son[j]!)Nj=1Cnum[j]1cnt[j]+num[j]1

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 10;
const int MOD = 1e9 + 7;
void add(LL &x, LL y) {
    x += y;
    if(x >= MOD) x -= MOD;
}
struct Edge {
    int from, to, val, next;
};
Edge edge[MAXN * 2];
int head[MAXN], edgenum;
void init() {memset(head, -1, sizeof(head)); edgenum = 0;}
void addEdge(int u, int v, int w) {
    Edge E = {u, v, w, head[u]};
    edge[edgenum] = E;
    head[u] = edgenum++;
}
LL pow_mod(LL a, int n) {
    LL ans = 1;
    while(n) {
        if(n & 1) {
            ans = ans * a % MOD;
        }
        a = a * a % MOD;
        n >>= 1;
    }
    return ans;
}
LL dp[MAXN];
LL fac[2000000 + 10];
int sum[MAXN];
LL C(int n, int m) {
    return fac[n] * pow_mod(fac[n - m], MOD - 2) % MOD * pow_mod(fac[m], MOD - 2) % MOD;
}
void DFS(int u, int fa) {
    dp[u] = 1; sum[u] = 0;
    for(int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if(v == fa) continue;
        DFS(v, u);
        sum[u] += edge[i].val;
        dp[u] = dp[u] * dp[v] % MOD;
        dp[u] = dp[u] * C(sum[v] + edge[i].val - 1, sum[v]) % MOD;
        dp[u] = dp[u] * fac[edge[i].val * 2] % MOD;
        dp[u] = dp[u] * pow_mod(fac[edge[i].val], MOD - 2) % MOD;
    }
    dp[u] = dp[u] * fac[sum[u]] % MOD;
}
int main()
{
    fac[0] = 1;
    for(int i = 1; i <= 2000000; i++) {
        fac[i] = fac[i - 1] * i % MOD;
    }
    int n;
    while(scanf("%d", &n) != EOF) {
        init();
        for(int i = 1; i <= n - 1; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, w);
        }
        DFS(1, -1);
        printf("%lld\n", dp[1]);
    }
    return 0;
}

I
题意:有 N 个数,第i个数的取值范围是 [a[i],b[i]] ,问你严格 LIS=k 的方案数,其中 k1N
思路: N 很小,我们可以先确定LIS的大小情况,枚举全排列 c[]
然后我们可以得到一个名次数组 r[c[i]]=i c[i] 小的元素是第 r[c[i]] 个数。
对于确定的全排列,按照名次数组 r[] 的顺序 (i<j) ,我们再加一个位置上的限制
即:若 r[i]>r[j] ,那么 xi>=xj ,反之 xi<xj
设置 dp[i][j] 为第 i 小的数取j的方案数,这样若
r[i1]>r[i] dp[i][j]=jk=1dp[i1][k]
r[i1]<r[i] dp[i][j]=j1k=1dp[i1][k]
暴力统计肯定会 T 的,只需在dp的时候维护前缀和即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 200;
const int MOD = 1e9 + 7;
void add(LL &x, LL y) {
    x += y;
    if(x >= MOD) x -= MOD;
}
LL ans[6];
LL dp[6][1000 + 10];
LL sum[6][1000 + 10];
int a[6], b[6];
int n;
int c[6], L[6], r[6];
void Work() {
    int len = 0;
    for(int i = 1; i <= n; i++) {
        L[i] = 1;
        for(int j = 1; j < i; j++) {
            if(c[j] < c[i]) {
                L[i] = max(L[i], L[j] + 1);
            }
        }
        len = max(len, L[i]);

        for(int j = 0; j <= 1000; j++) {
            dp[i][j] = 0;
        }
    }

    for(int i = 1; i <= n; i++) {
        r[c[i]] = i;
    }

    for(int i = a[r[1]]; i <= b[r[1]]; i++) {
        dp[1][i] = 1;
    }

    sum[1][0] = 0;
    for(int i = 1; i <= 1000; i++) {
        sum[1][i] = sum[1][i - 1] + dp[1][i];
    }

    for(int i = 2; i <= n; i++) {
        for(int j = a[r[i]]; j <= b[r[i]]; j++) {
            if(r[i - 1] > r[i]) {
                dp[i][j] = sum[i - 1][j];
            }
            else {
                dp[i][j] = sum[i - 1][j - 1];
            }
        }
        sum[i][0] = 0;
        for(int j = 1; j <= 1000; j++) {
            sum[i][j] = sum[i][j - 1] + dp[i][j];
        }
    }
    ans[len] += sum[n][b[r[n]]];
    //printf("%d %d\n", len, sum[n][b[r[n]]]);
}
int main()
{
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i], &b[i]);
            ans[i] = 0; c[i] = i;
        }
        do {
            Work();
        }while(next_permutation(c + 1, c + n + 1));
        for(int i = 1; i <= n; i++) {
            if(i > 1) printf(" ");
            printf("%lld", ans[i]);
        }
        printf("\n");
    }
    return 0;
}

J
题意: nn 矩阵,两种操作
1、 lrd , 位置 (xy) 的元素被位置 (x,(y+d)%n) 的元素替换, l<=x<=r0<=y<n
2、 lrd , 位置 (xy) 的元素被位置 ((x+d)%n,y) 的元素替换, 0<=x<nl<=y<=r

用十字链表记录,发现每次对于 l 行(列)和r行(列),每次只修改 n 次,其他行(列)只修改2次,这样每次操作只需要 O(n)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 10;
const int MOD = 1e9 + 7;
void add(LL &x, LL y) {
    x += y;
    if(x >= MOD) x -= MOD;
}
struct Node {
    int l, r, u, d, v;
};
Node a[201 * 201];
int id[201][201];
int n, q, root;
void Init() {
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            if(i == 0) {
                a[id[i][j]].u = -1;
            }
            else {
                a[id[i][j]].u = id[i - 1][j];
            }

            if(j == 0) {
                a[id[i][j]].l = -1;
            }
            else {
                a[id[i][j]].l = id[i][j - 1];
            }

            if(i == n - 1) {
                a[id[i][j]].d = -1;
            }
            else {
                a[id[i][j]].d = id[i + 1][j];
            }

            if(j == n - 1) {
                a[id[i][j]].r = -1;
            }
            else {
                a[id[i][j]].r = id[i][j + 1];
            }
        }
    }
}
int b[201];
void Work1(int L, int R, int D) {
    int s1 = root;
    for(int i = 0; i < L; i++) {
        s1 = a[s1].d;
    } //old start

    int s2 = s1;
    for(int i = 0; i < D; i++) {
        s2 = a[s2].r;
    } // new start

    if(L == 0) root = s2;
    int s3 = s1;
    for(int i = 0; i < n - 1; i++) {
        s3 = a[s3].r;
    } // old last
    a[s1].l = s3; a[s3].r = s1;

    if(L > 0) {
        int s = a[s1].u, t = s2;
        for(int i = 0; i < n; i++) {
            a[s].d = t; a[t].u = s;
            s = a[s].r; t = a[t].r;
        }
    }

    for(int i = L + 1; i <= R; i++) {
        s1 = a[s1].d; s2 = a[s2].d; s3 = a[s3].d;
        a[s1].l = s3; a[s3].r = s1;
    }

    if(R < n - 1) {
        int s = a[s1].d, t = s2;
        for(int i = 0; i < n; i++) {
            a[s].u = t; a[t].d = s;
            s = a[s].r; t = a[t].r;
        }
    }
}
void Work2(int L, int R, int D) {
    int s1 = root;
    for(int i = 0; i < L; i++) {
        s1 = a[s1].r;
    } //old start

    int s2 = s1;
    for(int i = 0; i < D; i++) {
        s2 = a[s2].d;
    } // new start

    if(L == 0) root = s2;
    int s3 = s1;
    for(int i = 0; i < n - 1; i++) {
        s3 = a[s3].d;
    } // old last
    a[s1].u = s3; a[s3].d = s1;

    if(L > 0) {
        int s = a[s1].l, t = s2;
        for(int i = 0; i < n; i++) {
            a[s].r = t; a[t].l = s;
            s = a[s].d; t = a[t].d;
        }
    }

    for(int i = L + 1; i <= R; i++) {
        s1 = a[s1].r; s2 = a[s2].r; s3 = a[s3].r;
        a[s1].u = s3; a[s3].d = s1;
    }

    if(R < n - 1) {
        int s = a[s1].r, t = s2;
        for(int i = 0; i < n; i++) {
            a[t].r = s; a[s].l = t;
            s = a[s].d; t = a[t].d;
        }
    }
}
void OutPut() {
    for(int i = 0; i < n; i++) {
        int s = root;
        for(int j = 0; j < n; j++) {
            if(j > 0) printf(" ");
            printf("%d", a[root].v);
            root = a[root].r;
        }
        root = a[s].d;
        printf("\n");
    }
}
int main()
{
    while(scanf("%d%d", &n, &q) != EOF) {
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                id[i][j] = i * n + j;
                a[id[i][j]].v = id[i][j];
            }
        }
        Init(); root = id[0][0];
        while(q--) {
            int op, L, R, D;
            scanf("%d%d%d%d", &op, &L, &R, &D);
            if(op == 1) {
                Work1(L, R, D);
            }
            else {
                Work2(L, R, D);
            }
        }
        OutPut();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值