• @codeforces



    @description@

    给定一个 n*n 的矩阵 A。

    我们称 A 是 magic 的,当且仅当:
    (1)A 是对称的。
    (2)A 的主对角线 (a_{ii} = 0)
    (3)对于每一组 (i, j, k) 满足 (a_{ij} le max{a_{ik}, a_{jk}})

    判断给出的 A 是不是 magic 的。

    Input
    第一行一个整数 n (1 ≤ n≤ 2500) 。
    接下来 n 行包含 n 个整数,描述矩阵 A。
    注意 A 不一定对称或者主对角线全为 0。
    Output
    如果是 magic 的,输出 "MAGIC";否则输出 "NOT MAGIC"。

    Examples
    Input
    3
    0 1 2
    1 0 2
    2 2 0
    Output
    MAGIC

    Input
    2
    0 1
    2 3
    Output
    NOT MAGIC

    @solution@

    首先第 1, 2 个条件直接判。主要是考虑第 3 个条件。

    解决这道题有一步很关键:将矩阵 A 看成一个图 G 的邻接矩阵。
    虽然隔壁的 zjx 大佬告诉我这个已经是套路了,可是菜如我并不熟悉这种套路。

    考虑假如不满足第 3 个条件,就有 w(i, j) > w(i, k) 且 w(i, j) > w(k, j)。
    即 (i, j) 与两个边权比它严格小的边形成了三元环。

    可以发现三元环这个条件限制太严格,不太好判断。
    假如 (i, j) 与若干个边权比它严格小的边形成了环,设形成的环为 i -> p1 -> p2 -> ... -> j -> i。这个情况是否也不合法呢?
    考虑边 (i, p2),如果 w(i, p2) >= w(i, j) 则 i -> p1 -> p2 -> i 本身就形成了不合法的三元环。
    如果 w(i, p2) >= w(i, j),则可以把环缩减为 i -> p2 -> ... p -> j -> i。递归验证必然可以验证到三元环。
    所以只要形成环就一定不合法。

    那么算法就很明晰了。将边权排序,从小到大加入边。
    如果加入到一条边形成了环,则不合法。
    同种边权先查询后同时加入。

    时间复杂度 (O(n^2log(n^2)))

    @accepted code@

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int MAXN = 2500;
    const int MAXM = MAXN*MAXN/2;
    struct edge{
    	int x, y, k;
    	friend bool operator < (const edge &a, const edge &b) {
    		return a.k < b.k;
    	}
    }e[MAXM + 5];
    int fa[MAXN + 5];
    int find(int x) {
    	return fa[x] = (fa[x] == x ? x : find(fa[x]));
    }
    void unite(int x, int y) {
    	int fx = find(x), fy = find(y);
    	if( fx != fy ) fa[fx] = fy;
    }
    int A[MAXN + 5][MAXN + 5];
    int main() {
    	int n, cnt = 0; scanf("%d", &n);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			scanf("%d", &A[i][j]);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++) {
    			if( A[i][j] != A[j][i] ) {
    				puts("NOT MAGIC");
    				return 0;
    			}
    			if( i == j && A[i][j] ) {
    				puts("NOT MAGIC");
    				return 0;
    			}
    		}
    	for(int i=1;i<=n;i++) {
    		for(int j=1;j<i;j++)
    			cnt++, e[cnt].k = A[i][j], e[cnt].x = i, e[cnt].y = j;
    		fa[i] = i;
    	}
    	sort(e + 1, e + cnt + 1);
    	e[cnt + 1].k = -1;
    	for(int i=1;i<=cnt;i++) {
    		int j = i;
    		while( e[j+1].k == e[i].k ) j++;
    		for(int k=i;k<=j;k++)
    			if( find(e[k].x) == find(e[k].y) ) {
    				puts("NOT MAGIC");
    				return 0;
    			}
    		for(int k=i;k<=j;k++)
    			unite(e[k].x, e[k].y);
    		i = j;
    	}
    	puts("MAGIC");
    }
    

    @details@

    其实这道题还可以用最小生成树的思路解。
    这样的话,用 prim 求最小生成树就是 O(n^2) 的,少个 log。
    但是时限 5s 就没必要了。

  • 相关阅读:
    awesome-blazor
    SQlite+dapper操作
    HashMap和HashTable的区别
    Linux常见命令大全
    多态的典型例题
    Hbase的安装及配置
    利用线程和管道的方式从客户端向服务的进行传送照片
    对TreeSet中的元素"HashSet"、"ArrayList"、"TreeMap"、"HashMap"、"TreeSet"、"LinkedList"进行升序 * 使用静态内部类实现
    对TreeSet中的元素"HashSet"、"ArrayList"、"TreeMap"、"HashMap"、"TreeSet"、"LinkedList"进行升序 *使用匿名内部类实现
    使用TreeSet和Comparator,写TreeSetTest1 要求:对TreeSet中的元素"HashSet"、"ArrayList"、"TreeMap"、 "HashMap"、"TreeSet"、"LinkedList"进行升序和倒序排列
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11789145.html
Copyright © 2020-2023  润新知