2022/8/8测试总结


T1.Deliver the Cake

分析题意

首先,如果忽略掉蛋糕拿在手上的不同状态,那么实际就是求最短路问题

但是,在每个村庄,拿蛋糕的状态我们必须考虑进去,所以将每个点的情况枚举,并按照不同的转移方式连边,就是我们需要做的


实现:


拆点

题目给出了ll, rr, mm三种不同的节点

ll, rr :对于每个 ll 或者 rr点,状态是确定的,故拆分成

ll : (ll, ll)

rr : (rr, rr)

mm : 将 mm 点拆分成 ll 和 rr 两个节点,并在图中建立相应的边

mm : (ll, rr)

连边

需要注意的是,我们将点拆分,实际拆分的是点的状态,也就是说类似的创造了一个新点,只不过同属于一个村庄的两种不同状态

所以,在连边时,实际有四种状态的边相连(每个点各有两种)

建立无向图,注意双向存边


代码实现

初始化

cnt = 0;
ans = INF;

拆点

for (int i = 1; i <= n; i++) {
     if (sc[i] == 'L')
    	 node[i] = 'L', node[i + n] = 'L';
     if (sc[i] == 'R')
    	 node[i] = 'R', node[i + n] = 'R';
     if (sc[i] == 'M')
    	 node[i] = 'L', node[i + n] = 'R';
}

存边

for (int i = 1; i <= m; i++) {
    int u, v;
    long long w;
    u = read();
    v = read();
    w = read();
    add(u, v, w);
    add(u, v + n, w);
    add(u + n, v, w);
    add(u + n, v + n, w);
}

void add(int x, int y, long long z) {
    ver[++cnt] = y;
    edgew[cnt] = z;
    nextw[cnt] = head[x];
    head[x] = cnt;
    // 双向存边
    ver[++cnt] = x;
    edgew[cnt] = z;
    nextw[cnt] = head[y];
    head[y] = cnt;
}

dijkstradijkstra

void dijkstra(int s) {
    for (int i = 0; i <= (n << 1); i++) {
        dis[i] = INF;
        vis[i] = 0;
    }
    dis[s] = 0;
    q.push(edge(0, s));
    while (q.size()) {
        int x = q.top().id;
        q.pop();
        if (vis[x])
            continue;
        vis[x] = 1;
        for (int i = head[x]; i != -1; i = nextw[i]) {
            int y = ver[i];
            int z = edgew[i];
            int u;
            if (node[x] == node[y])
                u = 0;
            else
                u = 1;
            if (dis[y] > dis[x] + z + u * cost) {
                dis[y] = dis[x] + z + u * cost;
                q.push(edge(dis[y], y));
            }
        }
    }
}

处理最短路

因为起点与终点的状态也被拆分,所以需要分类讨论

 dijkstra(s);
 ans = min(ans, dis[t]);
 ans = min(ans, dis[t + n]);
 dijkstra(s + n);
 ans = min(ans, dis[t]);
 ans = min(ans, dis[t + n]);
 write(ans);
 printf("\n");

最后,注意数据范围,双向存边


T2.Very Easy Graph Problem

题意分析

题目中给出第ii条边的权值为2^i2i是一个关键的突破点

由数学知识我们可以得到:\sum_1^k∑1k​ 2 ^ i2i < (2 ^ k2k * 22)

所以,对于输入的一条边(u, v)(u,v)的两个端点u, vu,v, 如果之前的边满足使它们联通,这条边的权值将不作贡献

由此,对于所有的起点ss, 终点tt(a[s] == 1a[s]==1 && a[t] == 0a[t]==0)所构成的一张图,是满足最小生成树的性质的,即所有的点任意联通,且满足权值总和最小


具体实现

那么,接下来,我们就需要统计最小生成树上每条边做出了贡献的次数

假设有一条边为(u, v)(u,v), uu为vv的父亲节点

记录以vv根节点的子树中00的个数为b[v].p0b[v].p0,b[v].p1b[v].p1同理

记录sum0sum0为整棵树中00的个数,sum1sum1同理

所以,这条边对答案的贡献就是

sum = sum + (sum0 - b[i].p0) * b[i].p1 * b[i].w;
sum %= mod;
sum = sum + (sum1 - b[i].p1) * b[i].p0 * b[i].w;
sum %= mod;

文字描述: 以vv为根节点的子树中00的个数 乘上 子树以外11的个数, 再加上 以vv为根节点的子树中11的个数 乘上 子树以外00的个数


代码实现

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define ll long long
const int N = 2e5 + 5, mod = 1e9 + 7;

int p, n, m, k, sum0, sum1;
int a[N], fa[N];
ll val;

struct node {
    int p0, p1;
    ll w;
};
node b[N];

struct node operator+(struct node x, struct node y) {
    struct node z;
    z.p0 = x.p0 + y.p0;
    z.p1 = x.p1 + y.p1;
    z.w = 0;
    return z;
}

vector<pair<int, ll> > e[N];

int find(int x) {
    if (fa[x] == x)
        return x;
    return fa[x] = find(fa[x]);
}

struct node dfs(int now, int pre) {
    int size = e[now].size();
    pair<int, ll> pa;
    int temp = k;
    k++;
    ll w = 0;
    for (int i = 0; i < size; i++) {
        pa = e[now][i];
        if (pa.first == pre) {
            w = pa.second;
            continue;
        }
        b[temp] = b[temp] + dfs(pa.first, now);
    }
    b[temp].w = w;
    if (a[now] == 0)
        b[temp].p0++;
    else
        b[temp].p1++;
    return b[temp];
}

int main() {
    scanf("%d", &p);
    while (p--) {
        val = 1;
        sum0 = sum1 = a[0] = k = 0;
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            fa[i] = i;
            if (a[i] == 1)
                sum1 += 1;
            if (a[i] == 0)
                sum0 += 1;
            b[i].p0 = b[i].p1 = b[i].w = 0;
            e[i].clear();
        }
        int u, v;
        for (int i = 1; i <= m; i++) {
            scanf("%d %d", &u, &v);
            val *= 2;
            val %= mod;
            if (find(u) == find(v))
                continue;
            fa[find(u)] = find(v);
            e[u].push_back(make_pair(v, val));
            e[v].push_back(make_pair(u, val));
        }
        dfs(1, -1);
        ll sum = 0;
        for (int i = 1; i < n; i++) {
            sum = sum + (sum0 - b[i].p0) * b[i].p1 * b[i].w;
            sum %= mod;
            sum = sum + (sum1 - b[i].p1) * b[i].p0 * b[i].w;
            sum %= mod;
        }
        printf("%lld\n", sum);
    }

    return 0;
}

我认为这道题应该提出来分析的内容

有关代码重点部分dfsdfs,我的一些思考

struct node dfs(int now, int pre) {
    int size = e[now].size();
    pair<int, ll> pa;
    int temp = k;
    k++;
    ll w = 0;
//    printf("%d %d %d\n", now, pre, temp);
    for (int i = 0; i < size; i++) {
        pa = e[now][i];
      // 这条边连接到当前点的父节点,记录下w,即题意分析中提及的: 
      // 当有边(u, v)在最小生成树中时,以v为根节点,讨论(u, v)这条边对答案的贡献 
        if (pa.first == pre) {
            w = pa.second;
            continue;
        }
      // 沿着祖孙关系,向下dfs搜索,直至将整个子树的所有(0, 1)情况累加
        b[temp] = b[temp] + dfs(pa.first, now);
    }
    b[temp].w = w;
    // 因为,当前遍历到的now节点,本身也属于其祖先节点的子树中的一部分
    // 所以要注意将now节点本身的情况统计进入数组,在向上回溯时,传递答案
    if (a[now] == 0)
        b[temp].p0++;
    else
        b[temp].p1++;
    return b[temp];
}

另外,temp是在记录每一条边的相关信息,作为我们统计答案的标识

ps:因为是树的搜索,每条边恰好被访问一次(本人最开始搞不太明白这个操作)

在统计答案时,不难发现我们枚举的点是11到n - 1n−1,也就是说,最后统计的是建立nn个点的最小生成树所需的n - 1n−1条边,每条边作出的贡献和

for (int i = 1; i < n; i++) {
 	sum = sum + (sum0 - b[i].p0) * b[i].p1 * b[i].w;
   sum %= mod;
   sum = sum + (sum1 - b[i].p1) * b[i].p0 * b[i].w;
   sum %= mod;
}

以下是,合并操作——运算符重载

struct node operator+(struct node x, struct node y) {
    struct node z;
    z.p0 = x.p0 + y.p0;
    z.p1 = x.p1 + y.p1;
    z.w = 0;
    return z;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值