第46届ICPC济南站部分题解


第46届ICPC济南站题解(部分)

补题通道在PTA的教育商店,这里会简单给出题意

C:Optimal Strategy

题意:有两个人正在玩游戏,他们每次可以从给定数组中取出一个数字,其值作为自己的得分,轮流取数。每个人都想自己的得分更高,问让自己得分更高的情况下,有几种取数方法

思路:组合计数

建议先自己模拟下过程,才好理解以下我说的话。

一步一步来,

当我先手时,如果最大值的个数有奇数个,那么我一定会去选最大的那个。

相对的,如果最大值的个数有偶数个,那么我可以先放着最大的不选。但是假如有一方先选了最大值,那么另外一个必须得先选最大值了。

所以初步得到,方案数和最大值的奇偶性有关。

当为偶数时候,可以先放着,等对面选了自己跟着选,或者反之。

当为奇数的时候,先手先选,然后又变成了偶数的情况。

所以我们可以先找出得分数字的组合(相同的数字没有位置的差别),最后乘以所有相同的数字的全排列即可。

所以现在需要解决子问题,找出得分数字的组合方案数即可。

从小到大取数字,当当前数字个数为奇数的时候,必须得排第一个,剩下的两个一组进行插入即可

最终的柿子也就是

\[\sum_{i = 1}^n cnt[i]! * C_{\sum_{j = 1}^{i - 1} + \lfloor \frac{cnt[i]}{2} \rfloor}^{\lfloor \frac{cnt[i]}{2} \rfloor} \]

解释下这个柿子的组合数部分吧

其实就是一个经典问题,把 \(n\) 个相同红色球插入到 \(m\) 个蓝色球中的方案数

答案就是 \(C_{m + n}^{n}\) 因为插入之后一共有 \(n + m\) 个,其中 \(n\) 个是红球,相当于从中取出 \(n\) 个位置放红球,剩下的按照原来位置排序即可

#include 
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair PII;
typedef pair PLL;
const int INF = 0x3f3f3f3f, N = 1e6 + 10;
const int MOD = 998244353;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}

LL f[N];

LL q_pow(LL a, LL b, LL p) {
    LL res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
    }
    return res;
}

LL get_inv(LL x) {return q_pow(x, MOD - 2, MOD);}

LL C(int n, int m) {
    return f[n] * get_inv(f[n - m]) % MOD * get_inv(f[m]) % MOD;
}

inline void solve() {
    int n; cin >> n;
    f[0] = 1;
    for (int i = 1; i <= n; i ++ ) f[i] = f[i - 1] * i % MOD;
    vector a(n + 1), cnt(n + 1);
    LL res = 1;
    for (int i = 1; i <= n; i ++ ) cin >> a[i], cnt[a[i]] ++;
    for (int i = 1; i <= n; i ++ ) {
        if (!cnt[i]) continue;
        res = res * f[cnt[i]] % MOD;
    }
    int num = 0;
    for (int i = 1; i <= n; i ++ ) {
        if (!cnt[i]) continue;
        int now = cnt[i] / 2;
        res = res * C(num + now, now) % MOD;
        num += cnt[i];
    }
    cout << res << endl;
}
int main() {
#ifdef DEBUG
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    auto now = clock();
#endif
    ios::sync_with_stdio(false), cin.tie(nullptr);
    cout << fixed << setprecision(2);
//    int T; cin >> T;
//    while (T -- )
        solve();
#ifdef DEBUG
    cout << "============================" << endl;
    cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}

D: Arithmetic Sequence

题意:给一个数组,对于每个数字来说,都有将该值+1或者-1操作,问将该数组变成等差数列的最小次数操作是多少。

思路:三分

容易想到,对于公差 \(d\) 来说,若 \(f(d)\) 表示公差为 \(d\) 值时候的最少操作次数,那么 \(f(d)\) 是含有一个极小值的函数,这个极小值就是我们要求的答案,所以想到三分 \(d\) 然后计算即可。

接下来讲 \(f(d)\) 的求解

我们定义一个数组 \(d[i]\) 表示首相为0,公差为 \(x\) 的时候的操作次数。

那么显然,取 \(d\) 的中位数作为最终数列的参照就是正解。(和货仓选址差不多原理)

那么接下来也没啥难度了,不过注意此题如果直接三分会爆long long,所以要上int128,或者在 \(n\) 较大的时候减少三分范围。

#include 
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair PII;
typedef pair PLL;
const int INF = 0x3f3f3f3f, N = 2e5 + 10;
const int MOD = 1e9 + 7;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}

LL a[N], d[N], c[N];
int n;

LL check(LL x) {
    for (int i = 1; i <= n; i ++ ) c[i] = x * i - (a[i] - a[1]);
    nth_element(c + 1, c + (n + 1) / 2, c + 1 + n);
    LL mid = c[(n + 1) / 2];
    LL res = 0;
    for (int i = 1; i <= n; i ++ ) res += abs(mid - c[i]);
    return res;
}

inline void solve() {
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    LL l = (LL)-1e13, r = (LL)1e13;
    while (l < r) {
        LL mid1 = l + (r - l) / 3;
        LL mid2 = r - (r - l) / 3;
        if (check(mid1) <= check(mid2)) r = mid2 - 1;
        else l = mid1 + 1;
    }
    cout << check(l) << endl;
}
int main() {
#ifdef DEBUG
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    auto now = clock();
#endif
    ios::sync_with_stdio(false), cin.tie(nullptr);
    cout << fixed << setprecision(2);
//    int T; cin >> T;
//    while (T -- )
        solve();
#ifdef DEBUG
    cout << "============================" << endl;
    cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}

J:Determinant

题意:给定一个行列式,再给一个行列式的值的绝对值,问你行列式的值应该是绝对值本身,还是绝对值的负数。

想法:高斯消元取模

其实很简单,但是如果直接用python计算就会T,因为数字巨大。

所以用高斯消元加个取模即可。

#include 
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair PII;
typedef pair PLL;
const int INF = 0x3f3f3f3f, N = 120;
const int MOD = 1e9 + 7;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}

LL q_pow(LL a, LL b, LL p) {
    LL res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
    }
    return res;
}

LL p[N][N];
int n;

LL inv(LL x) {return q_pow(x, MOD - 2, MOD);}

LL Gauss() {
    LL res = 1;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = i; j <= n; j ++ ) {
            if (p[j][i] != 0) {
                if (j == i) break;
                res = -res;
                for (int k = i; k <= n; k ++ ) swap(p[j][k], p[i][k]);
                break;
            }
        }
        if (p[i][i] == 0) return 0;
        LL now = inv(p[i][i]);
        for (int j = i + 1; j <= n; j ++ ) {
            LL tmp = p[j][i] * now % MOD;
            for (int k = i; k <= n; k ++ ) p[j][k] = (p[j][k] - p[i][k] * tmp % MOD + MOD) % MOD;
        }
        res = (res * p[i][i] % MOD + MOD) % MOD;
    }
    return res;
}

inline void solve() {
    cin >> n;
    LL now = 0;
    string s; cin >> s;
    for (auto ite : s) now = (now * 10 % MOD + ite - '0') % MOD;
    for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) cin >> p[i][j];
    if (Gauss() == now) cout << "+\n";
    else cout << "-\n";
}
int main() {
#ifdef DEBUG
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    auto now = clock();
#endif
    ios::sync_with_stdio(false), cin.tie(nullptr);
    cout << fixed << setprecision(2);
    int T; cin >> T;
    while (T -- )
        solve();
#ifdef DEBUG
    cout << "============================" << endl;
    cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}