BZOJ 1077 :[SCOI2008]天平 差分约束

title

BZOJ 1077

LUOGU 2474

Description

\(n\) 个砝码,均为 \(1\) 克, \(2\) 克或者 \(3\) 克。你并不清楚每个砝码的重量,但你知道其中一些砝码重量的大

小关系。你把其中两个砝码 \(A\)\(B\) 放在天平的左边,需要另外选出两个砝码放在天平的右边。问:有

多少种选法使得天平的左边重 \((c_1)\) 、一样重 \((c_2)\) 、右边重 \((c_3)\) ?(只有结果保证惟一的选法才统计在

内), \(n\leqslant 40\)

Input Format

第一行包含三个正整数 \(n,A,B\)\(1\leqslant A,B\leqslant N\)\(A\)\(B\) 不相等)。砝码编号为 \(1\sim N\)

以下 \(n\) 行包含重量关系矩阵,其中第 \(i\) 行第 \(j\) 个字符为加号 + 表示砝码i比砝码j重,减号 - 表示砝码 \(i\) 比砝码 \(j\) 轻,等号 = 表示砝码 \(i\) 和砝码 \(j\) 一样重,问号 ? 表示二者的关系未知。

存在一种情况符合该矩阵。

Output Format

仅一行,包含三个整数,即 \(c_1,c_2,c_3\)

analysis

很容易看出来是差分约束题目了,建图还算可以,关键是怎么统计方案,伤脑筋。


网上很多并查集写法的,思路基本一样了:

  1. 用并查集的一个好处就是可以将所有的相等条件合并,方便讨论,其他条件用二维数组表示两点关系;

  2. \(O(n^2)\) 暴力处理出每一个点的取值范围:如果一个点既有前驱,又有后继,那么一定取值为 \(2\) ,其他类同,然后更新其他的点;
  3. 枚举所有的 \(C,D\) ,判断和 \(A,B\) 的情况,以及方案是否唯一。


得益于数据范围极小, \(floyed\) 也是可以用的,十分简化 \(code\) ,因为在用二维数组完成建图后,只需要跑两遍 \(floyed\) 就可以得到最后的所有情况,然后 \(O(n^2)\) 枚举 \(C,D\) ,判断和 \(A,B\) 的情况,以及方案是否唯一即可。


不过数据范围大一些, \(floyed\) 就挂了,所以还有最正规的差分约束写法,用 \(spfa\) 来写了。

在用差分约束建好图后,跑一遍最短路,跑一遍最长路,然后只能枚举所有的情况,一个一个判断了,总的来说,复杂度还是 \(O(n^2)\) 的。

code

并查集。

#include <bits/stdc++.h>

#define file(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)

#define DeBug(x) std::cout << #x << " = " << x << std::endl

const int MaxN = 60;

namespace IO
{
    #define G ch = getchar()
    char buf[1<<15], *fs, *ft;
    inline char getc() { return ft == fs && (ft = (fs = buf) + fread(buf, 1, 1 << 15, stdin), ft == fs) ? 0 : *fs ++; }
    template <typename T> inline void read(T &x)
    {
        x = 0;
        T f = 1, G;
        while (!isdigit(ch) && ch ^ '-') G;
        if (ch == '-') f = -1, G;
        while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), G;
        x *= f;
    }

    template <typename T, typename... Args>
    inline void read(T &x, Args &...args) { read(x); read(args...); }

    char Out[1<<24], *fe = Out;
    inline void flush() { fwrite(Out, 1, fe - Out, stdout); fe = Out; }
    template <typename T> inline void write(T x, char str)
    {
        if (!x) *fe++ = 48;
        if (x < 0) *fe++ = '-', x = -x;
        T num = 0, ch[20];
        while (x) ch[++ num] = x % 10 + 48, x /= 10;
        while (num) *fe++ = ch[num --];
        *fe++ = str;
    }
}

using IO::read;
using IO::write;

template <typename T> inline bool chkMin(T &a, const T &b) { return a > b ? (a = b, true) : false; }
template <typename T> inline bool chkMax(T &a, const T &b) { return a < b ? (a = b, true) : false; }
template <typename T> inline T min(T a, T b) { return a < b ? a : b; }
template <typename T> inline T max(T a, T b) { return a > b ? a : b; }

int fa[MaxN];
inline int get(int x)
{
    return fa[x] == x ? x : fa[x] = get(fa[x]);
}

inline void merge(int i, int j)
{
    int x = get(i), y = get(j);
    if (x ^ y) fa[x] = y;
}

int mp[MaxN][MaxN];
char s[MaxN][MaxN];
int l[MaxN], r[MaxN];
int main()
{
    int n, A, B; read(n, A, B);
    for (int i = 1; i <= n; ++ i) fa[i] = i;
    for (int i = 1; i <= n; ++ i)
    {
        scanf("%s", s[i] + 1);
        for (int j = 1; j <= n; ++ j)
            if (s[i][j] == '=') merge(i, j);
    }
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= n; ++ j)
        {
            if (s[i][j] == '+') mp[get(i)][get(j)] = 1, mp[get(j)][get(i)] = -1;
            if (s[i][j] == '-') mp[get(i)][get(j)] = -1, mp[get(j)][get(i)] = 1;
        }
    for (int i = 1; i <= n; ++ i)
    {
        if (get(i) ^ i) continue;
        int pre = 0, suf = 0;
        for (int j = 1; j <= n; ++ j) pre |= (mp[i][j] == 1), suf |= (mp[i][j] == -1);
        if (!pre || !suf) continue;
        l[i] = 2;
        for (int j = 1; j <= n; ++ j)
        {
            if (mp[i][j] == 1) l[j] = 1;
            if (mp[i][j] == -1) l[j] = 3;
        }
    }
    for (int i = 1; i <= n; ++ i)
    {
        if (fa[i] ^ i) continue;
        if (l[i]) { r[i] = l[i]; continue; }
        l[i] = 1, r[i] = 3;
        for (int j = 1; j <= n; ++ j)
        {
            if (mp[i][j] == 1) l[i] = 2;
            if (mp[i][j] == -1) r[i] = 2;
        }
    }
    int a = fa[A], b = fa[B], c1 = 0, c2 = 0, c3 = 0;
    for (int C = 1; C <= n; ++ C)
    {
        if (C == A || C == B) continue;
        for (int D = C + 1; D <= n; ++ D)
        {
            if (D == A || D == B) continue;
            int c = fa[C], d = fa[D], id[] = {a, b, c, d}, t1 = 0, t2 = 0, t3 = 0;
            for (int vc = l[c]; vc <= r[c]; ++ vc)
                for (int vd = l[d]; vd <= r[d]; ++ vd)
                    for (int va = l[a]; va <= r[a]; ++ va)
                        for (int vb = l[b]; vb <= r[b]; ++ vb)
                        {
                            bool flag  = 1;
                            int v[] = {va, vb, vc, vd};
                            for (int i = 0; i < 4 && flag; ++ i)
                                for (int j = i + 1; j < 4 && flag; ++ j)
                                    if (id[i] == id[j] && v[i] ^ v[j]) flag = 0;
                            for (int i = 0; i < 4 && flag; ++ i)
                                for (int j = i + 1; j < 4 && flag; ++ j)
                                    if ((mp[id[i]][id[j]] == 1 && v[i] <= v[j])
                                        || (mp[id[i]][id[j]] == -1 && v[i] >= v[j])) flag = 0;
                            if (!flag) continue;
                            if (va + vb > vc + vd) t1 = 1;
                            if (va + vb == vc + vd) t2 = 1;
                            if (va + vb < vc + vd) t3 = 1;
                        }
            if (t1 + t2 + t3 == 1) c1 += t1, c2 += t2, c3 += t3;
        }
    }
    write(c1, ' '), write(c2, ' '), write(c3, '\n');
    IO::flush();
    return 0;
}

\(floyed\)

#include <bits/stdc++.h>

#define file(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)

#define DeBug(x) std::cout << #x << " = " << x << std::endl

const int MaxN = 50;

namespace IO
{
    #define G ch = getchar()
    char buf[1<<15], *fs, *ft;
    inline char getc() { return ft == fs && (ft = (fs = buf) + fread(buf, 1, 1 << 15, stdin), ft == fs) ? 0 : *fs ++; }
    template <typename T> inline void read(T &x)
    {
        x = 0;
        T f = 1, G;
        while (!isdigit(ch) && ch ^ '-') G;
        if (ch == '-') f = -1, G;
        while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), G;
        x *= f;
    }

    template <typename T, typename... Args>
    inline void read(T &x, Args &...args) { read(x); read(args...); }

    char Out[1<<24], *fe = Out;
    inline void flush() { fwrite(Out, 1, fe - Out, stdout); fe = Out; }
    template <typename T> inline void write(T x, char str)
    {
        if (!x) *fe++ = 48;
        if (x < 0) *fe++ = '-', x = -x;
        T num = 0, ch[20];
        while (x) ch[++ num] = x % 10 + 48, x /= 10;
        while (num) *fe++ = ch[num --];
        *fe++ = str;
    }
}

using IO::read;
using IO::write;

template <typename T> inline bool chkMin(T &a, const T &b) { return a > b ? (a = b, true) : false; }
template <typename T> inline bool chkMax(T &a, const T &b) { return a < b ? (a = b, true) : false; }
template <typename T> inline T min(T a, T b) { return a < b ? a : b; }
template <typename T> inline T max(T a, T b) { return a > b ? a : b; }

char s[MaxN];
int x[MaxN][MaxN], y[MaxN][MaxN];//n 较小,所以考虑以 floyed 来执行,x[i][j] 用以记录 j->i 的 Max,最长路求,y[i][j] 相反
int main()//得益于数据范围小,可以写的这么简洁,下面要试试数据范围大的了
{
    int n, a, b; read(n, a, b);
    for (int i = 1; i <= n; ++ i)
    {
        scanf("%s", s + 1);
        for (int j = 1; j <= n; ++ j)
        {
            if (i == j) continue;
            x[i][j] = -2, y[i][j] = 2;//砝码重量在 [1,3] 中,所以最大差值为 2,最小差值为 -2
            if (s[j] == '+') x[i][j] = 1;//i > j + 1
            if (s[j] == '-') y[i][j] = -1;//i < j - 1
            if (s[j] == '=') x[i][j] = y[i][j] = 0;//相等就不用说了
        }
    }
    for (int k = 1; k <= n; ++ k)
        for (int i = 1; i <= n; ++ i) if (i ^ k)
            for (int j = 1; j <= n; ++ j) if (i ^ j || j ^ k)
                chkMax(x[i][j], x[i][k] + x[k][j]), chkMin(y[i][j], y[i][k] + y[k][j]);//Floyed 处理最长路,最短路
    int c1 = 0, c2 = 0, c3 = 0;
    for (int i = 1; i <= n; ++ i)
        for (int j = i + 1; j <= n; ++ j)
        {
            if (i == j || i == a || i == b || j == a || j == b) continue;//暴力枚举,遇到 a,b 跳过
            if (x[a][i] > y[j][b] || x[a][j] > y[i][b]) ++ c1;//(a > i) > (j < b) 或者 (a > j) > (i < b) 符合左边重
            if ((x[a][i] == y[a][i] && x[a][i] == y[j][b] && x[j][b] == y[j][b])//a == i, (a > i) == (j < b), j == b -> 都相同
                || (x[a][j] == y[a][j] && x[a][j] == y[i][b] && x[i][b] == y[i][b])) ++ c2;//相反,不过表达的情况一样
            if (y[a][i] < x[j][b] || y[a][j] < x[i][b]) ++ c3;//(a < i) < (j > b) 或者 (a < j) > (i > b) 符合右边重
        }
    write(c1, ' '), write(c2, ' '), write(c3, '\n');
    IO::flush();
    return 0;
}

\(spfa\)\(Dijkstra\) 可跑不了最长路),不过我不想写了。

猜你喜欢

转载自www.cnblogs.com/G-hsm/p/BZOJ1077.html