P2474 [SCOI2008]天平 题解

题面

更好的阅读体验?

Solution

题目中的 “\(+\)” 和 “\(-\)” 都蕴含着大小关系,所以考虑差分约束。

对于每两个砝码之间有四种情况,我们可以分别列出他们的不等关系

  • 如果是 \(i = j\), 那么有 \(0 \le i - j \le 0\)
  • 如果是 \(i > j\),那么有 \(1 \le i - j \le 2\)
  • 如果是 \(i < j\), 那么有 \(-2 \le i - j \le -1\)
  • 如果是 \(?\),那么有 \(-2 \le i - j \le 2\)

不等式的范围取决于两个砝码 \(i,j\) 极限时能取的的情况

我们要知道在所有这些约束条件都满足的情况下,两个砝码之差的范围

那么需要一个最大值和最小值。

我们知道差分约束中最长路可以求最小值,最短路可以求最大值,那么根据上面的不等关系拆开建边即可。

这里采用 \(\text{Floyd}\) 求最短路,这样两点之间的差也好表示:

\(Dis_{i,j}\)\(j - i\) 可取的最小值, \(dis_{i,j}\)\(j - i\) 可取的最大值,即

\[Dis_{i,j} \le j-i \le dis_{i,j} \]

为什么要求两个砝码之差

当统计左边重的情况时,有 \(A+B>i+j\),稍微转换一下变成 \(A - i > j - B\),此时出现了差的形式。对于三个统计的情况都可以这样变换。

  • 当统计左边重时,有 \(A+B>i+j\),转化成 \(A-i>j-B\)。那么只有 \(A-i\) 的最小值大于 \(j-B\) 的最大值或者 \(B-i\) 的最小值大于 \(j - A\) 的最大值才有贡献。
  • 当统计右边重时,和左边的情况相反。类比推理即可。
  • 当统计两边一样重时,\(A+B = i+j\),转换成 \(A-i = B-j\)。那么只有 \(A-i\) 的最小值等于 \(B-j\) 的最大值并且 \(A-i\) 的最大值等于 \(B-j\) 的最小值 或者 \(A,B\)交换后成立的时候才有贡献。

\(A,B\) 匹配的 \(i,j\) 暴力枚举即可。

总的复杂度是 \(O(n^3)\)

更多细节看代码。

Code

/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, A, B;
char s[55][55];
int dis[55][55], Dis[55][55];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int main()
{
    n = read(), A = read(), B = read();
    for(int i = 1; i <= n; ++i) cin >> s[i] + 1;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
            if(s[i][j] == '=') {
                dis[i][j] = 0, Dis[i][j] = 0; // 0 <= i - j <= 0
            } else if(s[i][j] == '+') {
                dis[i][j] = -1, Dis[i][j] = -2; // 1 <= i - j <= 2
            } else if(s[i][j] == '-') {
                dis[i][j] = 2, Dis[i][j] = 1; // -2 <= i - j <= -1 
            } else {
                dis[i][j] = 2, Dis[i][j] = -2; // -2 <= i - j <= 2
            }
        }
    }
    for(int k = 1; k <= n; ++k) {
        for(int i = 1; i <= n; ++i) {
            if(i == k) continue;
            for(int j = 1; j <= n; ++j) {
                if(k == j || i == j) continue;
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); // 最短路跑最大值 
                Dis[i][j] = max(Dis[i][j], Dis[i][k] + Dis[k][j]); // 最长路跑最小值 
            }
        }
    }
    int cnt1 = 0, cnt2 = 0, cnt3 = 0;
    for(int i = 1; i <= n; ++i) {
        if(i == A || i == B) continue;
        for(int j = 1; j < i; ++j) { // 暴力枚举 让每组砝码都选一遍 
            if(j == A || j == B) continue;
            if(Dis[i][A] > dis[B][j] || Dis[i][B] > dis[A][j] ) // A + B > i + j -> A - i > j - B
                cnt1++;
            if((dis[i][A] == Dis[B][j] && Dis[i][A] == dis[B][j]) || (dis[i][B] == Dis[A][j] && Dis[i][B] == dis[A][j])) // A + B == i + j
                cnt2++;
            if(Dis[A][i] > dis[j][B] || Dis[B][i] > dis[j][A] ) // A + B < i + j -> i - A > B - j
                cnt3++;
        }  
    }
    printf("%d %d %d", cnt1, cnt2, cnt3);
    return 0;
}
posted @ 2021-06-08 21:52  Suzt_ilymtics  阅读(115)  评论(0编辑  收藏  举报