cf1131 D. Gourmet choice(并查集处理连通块+拓扑排序)


题意:

构造正整数数组 \(a[],b[]\) ,要求用到的最大数尽量小。对任意 \(i,j\)\(a_i\)\(b_j\) 的大小关系已知。

数组大小均小于1000

思路:

先处理所有相等关系。用并查集,把相等的元素放进同一连通块中,得到 idx 个连通块 \(blo_1,blo_2,\cdots ,blo_{idx}\)

然后处理所有不等关系。若 \(a_i ,则从 \(a_i\) 所在的连通块向 \(b_j\) 所在的连通块连边。对所有连通块做拓扑排序,一边排序一边填数(染色)。

图中可能有重边、反边、环,图也不一定连通,但都不用特殊处理。只在拓扑排序后判断就行。

#include 
using namespace std;
const int N = 1e3 + 5, M = 2 * N;
int n, m, p[M], idx, id[M], in[M], col[M], ans[M];
char a[N][N]; //输入
vector blo[M], ne[M]; //ne存图

int get(int x) {return (x==p[x]?x:(p[x]=get(p[x]))); }

int q[M];
bool topo()
{
    int hh = 0, tt = -1;
    for(int i = 1; i <= idx; i++) if(!in[i])
        q[++tt] = i, col[i] = 1; //给连通块染色
    while(hh <= tt) {
        int u = q[hh++];
        for(int v : ne[u]) if(--in[v] == 0)
            q[++tt] = v, col[v] = col[u] + 1;
    }
    return tt == idx - 1; //有无拓扑序
}

signed main()
{
    for(int i = 1; i < M; i++) p[i] = i; //init

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) {
            scanf(" %c", &a[i][j]);
            if(a[i][j] == '=') p[get(i)] = get(j+n); //合并相等元素
        }

    for(int i = 1; i <= n; i++) { //处理出连通块
        int fa = get(i);
        if(!id[fa]) id[fa] = ++idx; //记录每个祖先所在的连通块
        blo[id[fa]].push_back(i);
    }
    for(int i = 1; i <= m; i++) {
        int fa = get(i+n);
        if(!id[fa]) id[fa] = ++idx;
        blo[id[fa]].push_back(i+n);
    }

    for(int i = 1; i <= n; i++) //建图,记录入度
        for(int j = 1; j <= m; j++) {
            int bi = id[get(i)], bj = id[get(j+n)];
            if(a[i][j] == '<') ne[bi].push_back(bj), in[bj]++;
            if(a[i][j] == '>') ne[bj].push_back(bi), in[bi]++;
        }

    if(!topo()) return puts("No"), 0;

    for(int i = 1; i <= idx; i++) for(int j : blo[i]) ans[j] = col[i];

    puts("Yes"); for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
    puts(""); for(int i = 1; i <= m; i++) printf("%d ", ans[i+n]);

    return 0;
}