2017noip提高组模拟4Day1个人题解

本文解析了计蒜客NOIP三道经典算法题:质数统计、密室逃脱及树上路径重合点数计算。涉及筛法、状态压缩BFS及最近公共祖先算法。

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

突然发现计蒜客noip的题目不错,大都是带一点算法又不太难的内种(acm铜牌线或以下),可以扫一下芝士盲区。

 

T1 小X的质数

求[L,R]区间内是质数或2质数积的数的个数 r<=1e7 最多1e5组询问

很基础的题 筛法搞一搞就好 O(nloglogn)的筛法筛2次再处理前缀和

注意这个复杂度下要用bool数组不然会超时 线性欧拉筛应该不会卡常数

 

T2 小X的密室

字面意思 二进制状态压缩现在状态下有的钥匙和每个房间的钥匙 位运算判断钥匙是否满足及到达房间获得钥匙后的状态

然后bfs搜索就好 链表存边 状态数n*2^k(也大概是时间复杂度) 但k大的时候有很多状态不可能存在 常数很小 不用剪枝优化什么的 没有什么坑点

 

T3 小X的佛光

树(无向无环图)中给三点abc 求a到b路径和c到b路径中重合的点数(就是重合的边数+1) 点数和询问数都是2*10^5

显然标记走过的点判重是会超时的 想到树的最近公共祖先 分析一下 发现只要知道abc三点两两的最邻近祖先 根据各点及对应祖先在树上的深度 就可以O(1)的算出重合边数

Tarjan算法可以离线的O(n+q)的一次处理所有询问(链表存边和询问) 模板套一下 复杂度是2*10^5+3*2*10^5 可以接受

或用倍增方法求最近公共祖先 总复杂度O((n+q)logn) 但常数小 也可以过

对于每组询问要处理abc两两的最近公共祖先 数组开大点就好

代码比较麻烦但想法不难

 

T1:

#include<cstdio>

bool p[10000009], p1[10000009];

int a[10000009], i, j;

int main()

{

    for (i = 2; i < 10000009; i++)

        if (p[i] == 0)

            for (j = 2 * i; j < 10000009; j += i)

                p[j] = 1;

    for (i = 2; i < 10000009; i++)

        if (p1[i] == 0)

            for (j = 2 * i; j < 10000009; j += i)

                if (p[j / i]) p1[j] = 1;

    for (i = 2; i < 10000009; i++)

        a[i] = a[i - 1] + (p1[i] == 0);

    scanf("%*d");

    while (~scanf("%d%d", &i, &j))

        printf("%d\n", a[j] - a[i - 1]);

}

T2:

#include<cstdio>

#include<cstring>

#include<queue>

using namespace std;

int n, m, nn[5005], kk = 1, h[5005], vi[5005][1111];

struct { int v, ne, z; }a[6006];

struct node { int sta, x; };

void add(int u, int v, int z)

{

    a[kk].v = v, a[kk].z = z, a[kk].ne = h[u], h[u] = kk++;

}

int bfs()

{

    int i, st, xx, ans = 1 << 28;

    queue<node>q;

    memset(vi, -1, sizeof(vi));

    node ss, qq;

    ss.x = 1, ss.sta = nn[1], vi[1][nn[1]] = 0;

    q.push(ss);

    while (!q.empty())

    {

        ss = q.front(), q.pop();

        for (i = h[ss.x]; i; i = a[i].ne)

            if ((ss.sta&a[i].z) == a[i].z)

            {

                xx = a[i].v, st = ss.sta | nn[xx];

                if (vi[xx][st] == -1)

                {

                    vi[xx][st] = vi[ss.x][ss.sta] + 1;

                    qq.x = xx, qq.sta = st, q.push(qq);

                }

            }

    }

    for (i = 0; i < 1025; i++)

        if (vi[n][i] != -1)

            ans = min(ans, vi[n][i]);

    return ans;

}

int main()

{

    int  k, i, j, x, y, z, q, ans;

    scanf("%d%d%d", &n, &m, &k);

    for (i = 1; i <= n; i++)

        for (j = 0; j < k; j++)

            scanf("%d", &x), nn[i] <<= 1, nn[i] += x;

    for (i = 0; i < m; i++)

    {

        scanf("%d%d", &x, &y), z = 0;

        for (j = 0; j < k; j++)

            scanf("%d", &q), z <<= 1, z += q;

        add(x, y, z);

    }

    ans = bfs();

    if (ans == 1 << 28) puts("No Solution");

    else printf("%d\n", ans);

}

 

T3:

#include <cstdio> 

#include <cstring> 

#include<algorithm>

using namespace std;

#define mm 200005

struct { int u, v, w, lca, ne; }edge[mm << 2], edge1[mm << 3];

int head[mm], ip, head1[mm], ip1, m, n, fa[mm], vis[mm], ance[mm], dir[mm];

void add(int u, int v, int w)

{

    edge[ip].v = v, edge[ip].w = w, edge[ip].ne = head[u], head[u] = ip++;

}

void add1(int u, int v)

{

    edge1[ip1].u = u, edge1[ip1].v = v, edge1[ip1].lca = -1, edge1[ip1].ne = head1[u], head1[u] = ip1++;

}

int  find(int x)

{

    if (fa[x] == x)

        return x;

    return fa[x] = find(fa[x]);

}

void Union(int x, int y)

{

    x = find(x), y = find(y);

    if (x != y)

        fa[y] = x;

}

void tarjan(int u)

{

    int i, v, w;

    vis[u] = 1, ance[u] = fa[u] = u;

    for (i = head[u]; i != -1; i = edge[i].ne)

    {

        v = edge[i].v, w = edge[i].w;

        if (!vis[v])

            dir[v] = dir[u] + w, tarjan(v), Union(u, v);

    }

    for (i = head1[u]; i != -1; i = edge1[i].ne)

    {

        v = edge1[i].v;

        if (vis[v])

            edge1[i].lca = edge1[i ^ 1].lca = ance[find(v)];

    }

}

int main()

{

    int u, v, w, i, lca, lcb, ans, lcc;

    scanf("%d%d%*d", &n, &m);

    memset(head, -1, sizeof(head)), memset(head1, -1, sizeof(head1));

        for (i = 1; i < n; i++)

            scanf("%d%d", &u, &v), add(u, v, 1), add(v, u, 1);

        for (i = 0; i < m; i++)

        {

            scanf("%d%d%d", &u, &v, &w), add1(u, v), add1(v, u);

            add1(w, v), add1(v, w), add1(u, w), add1(w, u);

        }

        dir[1] = 0, tarjan(1);

        for (i = 0; i < m; i++)

        {

            u = edge1[i * 6].u, v = edge1[i * 6].v, lca = edge1[i * 6].lca;

            lcb = edge1[i * 6 + 2].lca, w = edge1[i * 6 + 2].u;

            lcc = edge1[i * 6 + 4].lca;

            ans = dir[v] - max(dir[lca], dir[lcb]) + 1;

            if (lca == lcb)

                 ans = dir[lcc] - dir[lca] + dir[v] - dir[lca] + 1;

            printf("%d\n", ans);

        }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值