2020-03-30 SDU week6

氪金带东

现在有n台电脑相互连通成树,要求你输出里面每个点和距离其最远点之间的距离

输入

输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。

5
1 1
2 1
3 1
1 1

输出

对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).

3
2
3
4
4

思路

考虑每个点在树中所能走的最远距离,那么就离不开树的直径。树的直径即是树最远的两点之间的距离,这样的话,树中的每个点能走的最远的距离都是要经过树的直径的,而且,最远的距离是该点到直径两点中其中一点的距离。
这样的话,随便从树中的一点开始,比如是我写的程序中的每次都从点1开始,找到距离其最远的点,从上述中已经知道,这一点肯定是直径中的一点了,那么接下来,从这一点开始找距离其最远的另一点,这另一点就是直径的另一边了。在寻找另一点的过程中,每次经过一点,那么就能把这条线的长度加上上一点到直径点的距离,这样的话,就记录了树的每个点到直径的一点的距离了,那么同理,当找到直径的另一点的时候,也这么做一遍,就得到了另一组数据,到最终输出的时候,比较两组数据的大小,输出每个点大的那个,就是答案。

总结

这道题考察树的直径问题。

代码

#include
#include
#include
#include
#define MAXN 40000
using namespace std;
//用链式前向星写
bool wri[12000];//用来记录哪些点访问过了
struct Edge {
    int u, v, nxt;
    long long w;
}Edges[MAXN];
int head[MAXN], tot;//tot是Edges的下标
long long nex[20000];
long long nex2[20000];
void init() {
    tot = 0;
    for (int i = 0; i < 20000; i++) head[i] = -1;
}
void add(int u, int v, long long w) {
    Edges[tot].u = u;
    Edges[tot].v = v;
    Edges[tot].w = w;
    Edges[tot].nxt = head[u];
    head[u] = tot;
    tot++;
}
int N;
int main() {
    while (cin>>N) {
        int ot;
        long long re;//用来装点和它的长度
        init();//初始化链式前向星
        for (int i = 2; i <= N; i++) {
            scanf("%d %lld", &ot, &re);
            add(i, ot, re);
            add(ot, i, re);
        }
        for (int i = 0; i < 12000; i++) {
            wri[i] = false;
            nex[i] = 0;
        }
        //第一次是不做记录的bfs,二三次才做
        queue th;
        th.push(1);
        long long maxn = 0, max2 = 0;//用来记录现在的最大路径长度
        long long maxp = 0, maxp2 = 0;//用来记录最大路径长度达到时的点
        while (!th.empty()) {
            int a = th.front();
            wri[a] = true;
            int c = head[a];
            int b;
            th.pop();
            while (c != -1) {
                b = Edges[c].v;
                if (wri[b]) {
                    c = Edges[c].nxt;
                    continue;
                }
                th.push(b);
                nex[b] = nex[a] + Edges[c].w;
                if (nex[b] > maxn) {
                    maxn = nex[b];
                    maxp = b;
                }
                c = Edges[c].nxt;
            }//第一次找到了距离最远的点
        }
        maxn = 0;
        for (int i = 0; i < 12000; i++) {
            nex[i] = 0;//清空下一个的列表
            wri[i] = false;
        }
        th.push(maxp);//从现在的点找到距离他最远的点
        while (!th.empty()) {
            int a = th.front();
            wri[a] = true;
            int c = head[a];
            int b;
            th.pop();
            while (c != -1) {
                b = Edges[c].v;
                if (wri[b]) {
                    c = Edges[c].nxt;
                    continue;
                }
                th.push(b);
                nex[b] = nex[a] + Edges[c].w;
                if (nex[b] > max2) {
                    max2 = nex[b];
                    maxp2 = b;
                }
                c = Edges[c].nxt;
            }//找到距离第一个找到的点最远的另一个点
        }
        for (int i = 1; i <= N; i++) {
            wri[i] = false;
            nex2[i] = 0;
        }
        th.push(maxp2);
        while (!th.empty()) {
            int a = th.front();
            wri[a] = true;
            int c = head[a];
            int b;
            th.pop();
            while (c != -1) {
                b = Edges[c].v;
                if (wri[b]) {
                    c = Edges[c].nxt;
                    continue;
                }
                th.push(b);
                nex2[b] = nex2[a] + Edges[c].w;
                c = Edges[c].nxt;
            }//找到距离第一个找到的点最远的另一个点
        }
        long long y;
        for (int i = 1; i <= N; i++)
        {
            y = max(nex[i], nex2[i]);
            printf("%lld\n", y);
        }
    }
}

戴好口罩!

现有n个学生,他们之中有m个学生团体,病毒能在学生团体中传播,现在有一个0号病人,输出因为这个病人而导致染病的学生数量

输入

多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

输出

输出要隔离的人数,每组数据的答案输出占一行

4
1
1

思路

这道题考察并查集,学生团体就是一个集合,那么只要判断0号病人所在的团体还有这些团体中的人所在的团体的人数就是答案。

总结

涉及到集合的问题使用并查集能显著降低程序的复杂度。

代码

#include
using namespace std;
int sim[99999];
int find(int a) {
    if (sim[a] == a)
        return a;
    return sim[a] = find(sim[a]);
}
int main() {
    while (true)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        if (a == 0 && b == 0)
            break;
        for (int i = 0; i <= a; i++)
            sim[i] = i;
        for (int i = 0; i < b; i++) {
            int c;
            int d;
            scanf("%d", &c);
            c--;
            scanf("%d", &d);//取出第一个输入的群体成员,把他的根作为群体的根
            d = find(d);
            int e;
            for (int k = 0; k < c; k++) {
                scanf("%d", &e);
                sim[find(e)] = d;
            }
        }
        int sum = 0;
        int H = find(0);
        for (int i = 0; i <= a; i++) {
            if (find(i) == H)
                sum++;
        }
        printf("%d\n", sum);
    }
}

掌握魔法の东东 I

现有n块田,可以直接引水来对他浇灌,直接饮水要消耗mp,也可以从其他的田建传送门,这也要消耗mp,先要求输出要消耗的最小mp值

输入

第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

输出

9

思路

如果没有能直接引导水到田里这个选项,那么就是最小生成树问题了,但是现在多了这个选项之后,就可以假象一个点,从这个点可以直接引水到任何一个田,也就是题意中引水的第一种情况,这样的话,那么就能把第一种情况纳入到第二种的范围内,用最小生成树的kruscal算法实现了。

总结

本题做了巧妙转换,把第一种情况化为普通情况,这样就能使用kruscal计算了。

代码

#include
using namespace std;
int sim[99999];
int find(int a) {
    if (sim[a] == a)
        return a;
    return sim[a] = find(sim[a]);
}
int main() {
    while (true)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        if (a == 0 && b == 0)
            break;
        for (int i = 0; i <= a; i++)
            sim[i] = i;
        for (int i = 0; i < b; i++) {
            int c;
            int d;
            scanf("%d", &c);
            c--;
            scanf("%d", &d);//取出第一个输入的群体成员,把他的根作为群体的根
            d = find(d);
            int e;
            for (int k = 0; k < c; k++) {
                scanf("%d", &e);
                sim[find(e)] = d;
            }
        }
        int sum = 0;
        int H = find(0);
        for (int i = 0; i <= a; i++) {
            if (find(i) == H)
                sum++;
        }
        printf("%d\n", sum);
    }
}

| 数据中心 |

现在有n个点,要求出这n个点组成一个最小生成树,并输出最大权的边


image.png

image.png

输入

4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2

输出

4

思路

这道题本意是求最小瓶颈生成树,但是最小生成树一定是最小瓶颈生成树,但是最小瓶颈生成树却不一定是最小生成树,所以这道题只需要简单使用kruscal算法就能解决。

代码

#include
#include
using namespace std;
int par[310];//做kruscal的并查集
struct Edge{
    int u;
    int v;
    int elm;
    bool operator < (const Edge& p)const {
        if (elm != p.elm) return elm < p.elm;
        if (u != p.u) return u < p.u;
        return v < p.v;
    }
}Edges[90000];
int n;
int find(int a) {
    if (par[a] == a) return a;
    return par[a] = find(par[a]);
}
bool unite(Edge m) {
    int x = find(m.u);
    int y = find(m.v);
    if (x == y)
        return true;
    par[x] = y;
    return false;
}
int main() {
    scanf_s("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf_s("%d", &Edges[i].elm);
        Edges[i].u = 0;
        Edges[i].v = i;
    }
    int a;//用来去除不要的信息
    int b = n+1;//计数器
    for(int i=1;i<=n;i++)
        for (int j = 1; j <= n; j++) {
            if (i > j) {
                Edges[b].u = i;
                Edges[b].v = j;
                scanf_s("%d", &Edges[b].elm);
                b++;
            }
            else
                scanf_s("%d", &a);
        }
    sort(Edges + 1, Edges + b);
    for (int i = 0; i < 310; i++)
        par[i] = i;
    int num = 0;
    int sum = 0;
    a = 1;
    while (num!=n) {
        while (unite(Edges[a]))
            a++;
        sum += Edges[a].elm;
        num++;
    }
    printf("%d", sum);
}

题意

从瑞神家打牌回来后,东东痛定思痛,决定苦练牌技,终成赌神!
东东有 A × B 张扑克牌。每张扑克牌有一个大小(整数,记为a,范围区间是 0 到 A - 1)和一个花色(整数,记为b,范围区间是 0 到 B - 1。
扑克牌是互异的,也就是独一无二的,也就是说没有两张牌大小和花色都相同。
“一手牌”的意思是你手里有5张不同的牌,这 5 张牌没有谁在前谁在后的顺序之分,它们可以形成一个牌型。 我们定义了 9 种牌型,如下是 9 种牌型的规则,我们用“低序号优先”来匹配牌型,即这“一手牌”从上到下满足的第一个牌型规则就是它的“牌型编号”(一个整数,属于1到9):
同花顺: 同时满足规则 2 和规则 3.
顺子 : 5张牌的大小形如 x, x + 1, x + 2, x + 3, x + 4
同花 : 5张牌都是相同花色的.
炸弹 : 5张牌其中有4张牌的大小相等.
三带二 : 5张牌其中有3张牌的大小相等,且另外2张牌的大小也相等.
两对: 5张牌其中有2张牌的大小相等,且另外3张牌中2张牌的大小相等.
三条: 5张牌其中有3张牌的大小相等.
一对: 5张牌其中有2张牌的大小相等.
要不起: 这手牌不满足上述的牌型中任意一个.
现在, 东东从A × B 张扑克牌中拿走了 2 张牌!分别是 (a1, b1) 和 (a2, b2). (其中a表示大小,b表示花色)
现在要从剩下的扑克牌中再随机拿出 3 张!组成一手牌!!
其实东东除了会打代码,他业余还是一个魔法师,现在他要预言他的未来的可能性,即他将拿到的“一手牌”的可能性,我们用一个“牌型编号(一个整数,属于1到9)”来表示这手牌的牌型,那么他的未来有 9 种可能,但每种可能的方案数不一样。
现在,东东的阿戈摩托之眼没了,你需要帮他算一算 9 种牌型中,每种牌型的方案数。
我无法简单描述这些东西,只能大家看了

输入

第 1 行包含了整数 A 和 B (5 ≤ A ≤ 25, 1 ≤ B ≤ 4).
第 2 行包含了整数 a1, b1, a2, b2 (0 ≤ a1, a2 ≤ A - 1, 0 ≤ b1, b2 ≤ B - 1, (a1, b1) ≠ (a2, b2)).

5 2
1 0 3 1

输出

输出一行,这行有 9 个整数,每个整数代表了 9 种牌型的方案数(按牌型编号从小到大的顺序)

0 8 0 0 0 12 0 36 0

思路

使用数学!每种牌都是一种排列组合,只要算对了公式,那么结果就对了。

代码

#include
#include
using namespace std;
int a1, b1, a2, b2;
long long A, B;
long long sim[10];//用来装9种牌型各多少个
void pan() {
    //如果两张牌的大小差距过大或者大小一样的话就不能成为顺子
    if (a1 == a2 || abs(a2 - a1) > 4)
        sim[2] = 0;
    else
    {
        if (a1 > a2) {
            int temp = a2;
            a2 = a1;
            a1 = temp;
        }//a2是大的那个
        int th = a1+4;
        int sum = 0;
        for (long long i = 0; i < (A + 4); i++)
        {
            if ((i + 4) >= a2 && i <= a1 && (i + 4) < A)
                sum++;
        }
        sim[2] = sum * B * B * B;
        if (b1 == b2)
            sim[1] = sum;
        else
            sim[1] = 0;
    }
    //3的每个位置都能有B种情况
    if (b1 != b2)
        sim[3] = 0;
    else
        sim[3] = (A - 2) * (A - 3) * (A - 4) / 6;
    //如果两张牌已经大小相等了,那就只需要计算3张
    //炸弹必须要花色大于4种 
    if (B >= 4) {
        if (a1 == a2)
            sim[4] = ((B - 2) * (B - 3) / 2) * (A - 1);//修改 ((B - 2) * (B - 3) / 2) * (A - 1)
        else //两种情况相加
            sim[4] = (B - 1) * (B - 2) * (B - 3) / 3;
    }
    else sim[4] = 0;
    //三带二
    if (B >= 3) {
        if (a1 == a2)
            sim[5] = B * (B - 1) * (B - 2) * (A - 1) / 6 + B * B * (B - 1) / 2;//修改B * (B - 1) * (B - 2) * (A - 1) / 6 + B * B * (B - 1) / 2
        else
            sim[5] = (B - 1) * (B - 1) * (B - 2);
    }
    else
        sim[5] = 0;
    //两对
    if (B >= 2) {
        if (a1 == a2)
            sim[6] = B * B * (B - 1) * (A - 2) * (A - 1) / 2;
        else
            sim[6] = 2 * B * (B - 1) * (B - 1) * (A - 2);
    }
    else sim[6] = 0;
    //三条
    if (B >= 3) {
        if (a1 == a2)
            sim[7] = B * B * (B - 2) * (A - 1) * (A - 2) / 2;
        else
            sim[7] = (B - 1) * (B - 2) * (A - 2) * B * 7 / 6;
    }
    else
        sim[7] = 0;
    if (B >= 2) {
        if (a1 == a2)
            sim[8] = (A - 1) * (A - 2) * (A - 3) / 6 * B * B * B;
        else
            sim[8] = 3 * (A - 2) * (A - 3) * B * B * (B - 1) / 2;
    }
    else
        sim[8] = 0;
    int c = A * B;
    sim[9] = (c - 2) * (c - 3) * (c - 4) / 6;
    sim[2] -= sim[1];
    sim[3] -= sim[1];
    for (int i = 1; i < 9; i++)
        sim[9] -= sim[i];
    for (int i = 1; i < 9; i++)
        cout << sim[i] << " ";
    cout << sim[9];
}
int main() {
    cin >> A >> B;
    cin >> a1 >> b1 >> a2 >> b2;
    pan();
}

你可能感兴趣的:(2020-03-30 SDU week6)