2020 Multi-University Training Contest 2 - Total Eclipse

传送门

题意:

  • 有n个城市,m条道路,每个城市都有一个权值,现在要让每个城市的权值变成0,可执行的操作为:每次选择一个城市,使从这个城市出发能到的所有城市的权值-1,注意:当某个城市的权值变为0之后,它将不能被经过,问最少要几次操作使得所有城市的权值都变成0。

思路:

  • 先把题意抽象出来,也就是每次选择一个连通块,连通块中的点的权值-1,当某个点的权值为0,就不能算在连通块内。由于权值变为0的可能导致不连通的情况,每次选的权值肯定是整个连通块内的最小权值。
  • 我们可以将整个过程倒过来看,从权值最大的点开始依次加入每个点,新加入的点作为和它直接相连的且权值比它大的点的父节点,由于我们将点的权值从大到小排序,所以先遍历的肯定是权值大的点,最后把每个节点的权值与其父节点的权值的差求和就行了。

样例:

6
4 4
2 6 1 6
1 2
1 4
2 3
3 4

2020 Multi-University Training Contest 2 - Total Eclipse_第1张图片
2020 Multi-University Training Contest 2 - Total Eclipse_第2张图片

  • 从上面样例跑的结果来看,很明显我们能看出,处理的节点顺序为2->4->1->3。
  • 第一个处理的点为节点2,当前与节点2直接相连的点没有进入连通块(并查集)里,所以f数组和fa数组没有改变
  • 第二个处理的点为节点4,与节点2同理。
  • 第三个处理的点为节点1,此时与节点1直接相连并且已经遍历过的点有节点2和4,开始操作,将节点2和4的f以及fa数组值记为x,也就是此时的节点1。
  • 最后处理的点为节点3,与节点3直接相连的点也是2和4,找到y点,也就是并查集中的权值最小点,都是节点1,将节点1的f以及fa数组记为节点3。
  • 这里有个无关紧要的问题,就是为什么最后节点2的f数组值也变了,因为在处理节点3的时候,先遍历的相连的点是节点4,节点4的f数组值是1,所以1的f数组值被标记成3,再遍历到节点2的时候,节点2的f数组还是1,所以2的f变成了3。
#include
using namespace std;
#define ll long long

int n, m;
int a[100010];  // 存储每个点的权值
int q[100010];  // 存储每个点的下标

// 链式前向星
int cnt;
int g[100010];
int v[400050];
int ne[400050];

int f[100010];      // 并查集数组
int fa[100010];     // 每个点的父节点
int vis[100010];    // 标记访问过的点             

int F(int x)    // 并查集查找
{
    return f[x]==x?x:f[x] = F(f[x]);
}

void add(int x, int y)  // 链式前向星加边
{
    v[++cnt] = y;
    ne[cnt] = g[x];
    g[x] = cnt;
}

bool cmp(int x, int y)  // 将城市按照权值从大到小排序
{
    return a[x] > a[y];
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        cnt = 0;
        scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            // 初始化数组
            q[i] = f[i] = i;    
            g[i] = fa[i] = vis[i] = 0;
        }
        int x, y;
        while(m--)
        {   
            scanf("%d %d", &x, &y);
            // 建图
            add(x,y);
            add(y,x);
        }
        // 将点按照权值从大到小排序
        sort(q+1,q+n+1,cmp);    

        for(int i = 1; i <= n; i++)
        {
            x = q[i];   // 取点
            vis[x] = 1; // 标记

            // 遍历与该点直接相连的点
            for(int j = g[x]; j; j = ne[j])
            {
                y = v[j];
                if(!vis[y]) continue; 
                y = F(y);
                if(y==x) continue;
                fa[y] = f[y] = x;
            }
			cout << q[i] << ":" << endl;
            for(int j = 1; j <= n; j++)
            {
                cout << j << "->" << fa[j] << "->" << f[j] << endl;
          	}
        }
        ll ans = 0;
        printf("%lld\n", ans);
    }
    return 0;
}

你可能感兴趣的:(题解)