title
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
很容易看出来是差分约束题目了,建图还算可以,关键是怎么统计方案,伤脑筋。
网上很多并查集写法的,思路基本一样了:
用并查集的一个好处就是可以将所有的相等条件合并,方便讨论,其他条件用二维数组表示两点关系;
- \(O(n^2)\) 暴力处理出每一个点的取值范围:如果一个点既有前驱,又有后继,那么一定取值为 \(2\) ,其他类同,然后更新其他的点;
枚举所有的 \(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\) 可跑不了最长路),不过我不想写了。