A. Access Levels

题意

有 $n$ 个程序员,$m$ 个文档。

给定一个 $n \times m$ 的矩阵 $a$,其中 $a_{ij} = 1$ 表示程序员 $i$ 可以访问文档 $j$,否则不行。

现在我们需要选择一个数字 $k$,使得总共有 $k$ 个access group。

然后将每个文档放入这 $k$ 个access group中的一个,并且给这个文档设定一个权限值 $c_i$。

然后给每个程序员设定 $k$ 个数字 $v_i$,代表这个程序员在使用第 $i$ 个group时拥有 $v_i$ 的权限值。

当一个程序员在访问一个文档 $i$ 时,如果这个文档所在的group为 $j$,那么需要保证这个程序员的 $v_j \geq c_i$ 才能访问,否则不能。

求一个最小的 $k$,使得访问矩阵可以被满足,并且给出具体方案。

其中,$n,m \leq 500$。

题解

注意到 group 与 group 之间是独立的,因为每个文档仅能属于一个group,所以我们分 group 来考虑。

注意到,如果一个group内有 $d$ 个文档 $i_1, i_2, …, i_d$,那么这些文档对于程序员们来说一定遵循:访问关系为单调为超集。

举个例子,在第二个样例中

2 3
101
100

那么假设这三个文档都在同一个group内,那么按照列来看,就可以得到 11, 00, 10 这三个数字,可以发现 11 > 10 > 00(这里的 11 > 10 是指 1110 的超集)。

这说明这三个文档可以在同一个group内。

这让我们想到了什么?如果我们将每一列看作一个 bitmask,那么如果 $b_1 > b_2$ 则可以由 $b_1$ 向 $b_2$ 连一条边,这就是一个DAG中的最小路径覆盖问题,每个路径就是一个 group。

剩下的就是很麻烦的实现了,注意去重之类的问题。

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e2+5;
int n, m;
char a[maxn][maxn];
map<string, int> mask_to_idx;
string masks[maxn];

// return 1 if b < a (b is subset of a)
bool is_sub(string& a, string& b) {
    for (int i = 1; i <= n; i++) {
        if (a[i-1] < b[i-1]) return 0;
    }
    return 1;
}

int par[maxn];
int finds(int u) {
    if (par[u] == u) return u;
    return par[u] = finds(par[u]);
}
void unions(int u, int v) {
    u = finds(u), v = finds(v);
    if (u == v) return;
    par[v] = u;
}
vector<int> adj[maxn<<1];
int vis[maxn<<1], visid = 0, match[maxn<<1], from[maxn<<1];
bool dfs(int u) {
    for (int v : adj[u]) {
        if (vis[v] == visid) continue;
        vis[v] = visid;
        if (!match[v] || dfs(match[v])) {
            match[v] = u;
            return 1;
        }
    }
    return 0;
}
int groupid = 0;
vector<int> groups[maxn];
int belongs[maxn];
int pri[maxn][maxn];  // privilege for the users
int privilege[maxn];  // privilege for each software

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        string s; cin >> s;
        for (int j = 1; j <= m; j++) {
            a[i][j] = s[j-1];
        }
    }
    int id = 0;
    for (int j = 1; j <= m; j++) {
        string mask = "";
        for (int i = 1; i <= n; i++) {
            mask += a[i][j];
        }
        mask_to_idx[mask] = ++id;
        masks[id] = mask;
    }
    for (int i = 1; i <= m; i++) par[i] = i;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= m; j++) {
            if (i == j) continue;
            if (masks[i] == masks[j]) {
                unions(i, j);
            }
        }
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= m; j++) {
            if (i == j) continue;
            if (par[i] == i && par[j] == j && is_sub(masks[i], masks[j])) {
                adj[i].push_back(j+m);
            }
        }
    }
    // 最小路径覆盖
    int ans = 0;  // 记录有多少个独立的parent
    for (int i = 1; i <= m; i++) {
        if (par[i] == i) ans++;
    }
    for (int i = 1; i <= m; i++) {
        if (par[i] == i) {
            visid++;
            ans -= dfs(i);
        }
    }

    for (int i = m+1; i <= 2*m; i++) {
        if (par[i-m] == i-m) {
            if (match[i]) {
                from[match[i]] = i-m;
            }
        }
    }

    for (int i = m+1; i <= 2*m; i++) {
        if (par[i-m] == i-m) {
            if (!match[i]) {
                int j = i-m;
                groupid++;
                while (j) {
                    groups[groupid].push_back(j);
                    belongs[j] = groupid;
                    j = from[j];
                }
            }
        }
    }

    cout << groupid << "\n";
    for (int i = 1; i <= m; i++) {
        belongs[i] = belongs[finds(i)];
        cout << belongs[i] << " ";
    }
    cout << "\n";
    // 已经找到了每个column所在的group

    for (int g = 1; g <= groupid; g++) {
        // 现在处理第 g 个group

        vector<pair<string, int>> vec;  // vector of masks for each user (with selected column in this group)
        for (int i = 1; i <= n; i++) {
            string mask = "";
            for (int j : groups[g]) {
                mask += a[i][j];
            }
            vec.push_back({mask, i});
        }
        sort(vec.begin(), vec.end());
        reverse(vec.begin(), vec.end());

        int pre_pri = 505;
        // 先处理 vec[0]
        
        string s = vec[0].first;  // 长度为 groups[g] 的长度
        pri[vec[0].second][g] = pre_pri - 1;
        for (int k = 0; k < groups[g].size(); k++)  {
            int j = groups[g][k];
            
            if (s[k] == '0') {
                privilege[j] = pre_pri;
            } 
        }

        for (int i = 1; i < n; i++) {
            s = vec[i].first;
            if (s == vec[i-1].first) {  // 如果和前面一个一样,直接复制
                pri[vec[i].second][g] = pri[vec[i-1].second][g];
            } else {
                pre_pri--;
                for (int k = 0; k < groups[g].size(); k++)  {
                    int j = groups[g][k];
                    if (!privilege[j] && s[k] == '0') {
                        privilege[j] = pre_pri;
                    }
                }
                pri[vec[i].second][g] = pre_pri - 1;
            }
        }

        for (int j : groups[g]) {
            if (!privilege[j]) privilege[j] = 1;
        }
    }


    // output answer
    for (int i = 1; i <= m; i++) {
        if (privilege[i] == 0) {
            privilege[i] = privilege[finds(i)];
        }
        cout << (privilege[i]) << " ";
    }
    cout << "\n";

    for (int i = 1; i <= n; i++) {
        for (int g = 1; g <= groupid; g++) {
            cout << pri[i][g] << " ";
        }
        cout << "\n";
    }
}