CSP-S模拟14

题面非常的良心,但依旧爆零

T1.莓良心

题目背景:我打扮成你喜欢的样子来看你了,广,不,da,darling...

我看到这个题想到了中位数的结论,甚至写了三分+对顶堆+树状数组,但是很假,对拍一组就挂了。这貌似是T1……赛后发现正解是贪心。首先取出\(minr\)\(maxl\),如果\(minr>maxl\),说明所有区间都覆盖了\([maxl, minr]\);如果不是,说明这两个区间隔离,而其他的区间在\([minr, maxl]\)有覆盖。设它们之间的距离\(sum=maxl-minr\),考虑这两个点的贡献:其他点在两侧时,到他们的距离为\(sum+δ\);在中间时,距离为\(sum\);显然后者更优。再讨论两个点左右浮动,这样只会使\(sum\)更大。所以得出结论:每次取出\(minr\)\(maxl\),将这两个点固定,其他的点都放在这段区间内,计算贡献,递归处理。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std; typedef pair<int, int> paint;
const int Z = 3e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }

int n, m, ans;
bool vs[Z];
priority_queue < paint, vector<paint>, greater<paint> > r;//小根堆(minr)
priority_queue < paint, vector<paint>, less<paint> > l;//大根堆(maxl)

sandom main()
{
    n = read();
    for (re i = 1; i <= n; i++) l.push(make_pair(read(), i)), r.push(make_pair(read(), i));
    while (!l.empty() && !r.empty())
    {
        while (!l.empty() && vs[l.top().second]) l.pop();
        while (!r.empty() && vs[r.top().second]) r.pop();
        vs[l.top().second] = vs[r.top().second] = 1;
        int ml = l.top().first, mr = r.top().first;
        if (ml <= mr) break;
        ans += (n - 1) * (ml - mr);
        n -= 2; l.pop(), r.pop(); 
    }
    cout << ans;
    return 0;
}

T2.尽梨了

题目背景:我成为你心中的第一位了吗?

集训队胡策题?枚举每一行选\(c\)\(1\),扫描所有的行,设当前行中\(1\)的个数为\(k\),分类讨论:1.\(k < c\),有几位上矩阵中为\(0\),而\(b\)\(1\),所以该行\(a\)为0;2.\(k > c\),有几位上矩阵中为\(1\),而\(b\)\(0\),所以该行\(a\)为1;3.\(k = c\),此时\(b\)可以唯一确定,并反推出其他行的一些信息。
现在来计算方案,1.存在\(k = c\)的情况,所有的这种类型的行必须完全相同,记数量为\(tot\),方案为\(2^{tot}\),否则无解。2.不存在\(k = c\)的情况,那么此时\(b\)不确定,我们把不能确定的位记为\(-1\),我们计算出\(b\)中所有\(k < c\)的行至少需要多少个1,记为\(cnt_1\),剩下的的\(i-cnt_1\)位就是还需要的\(-1\);计算出所有\(k > c\)的行至少需要多少个0,剩下的\(1\)记为\(cnt_2\)就是可以有的\(-1\);那么我们从可以选的\(cnt_2\)\(-1\)里选出\(i-cnt_1\)个,即\(C_{cnt_2}^{i-cnt_1}\)
因为复杂度是\(O(n^3)\)的,所以需要预处理:设\(pre[i]\)表示\(k\)小于等于\(i\)的集合并,\(suf[i]\)表示\(k\)大于等于\(i\)的集合并,有解仅当\(suf\)中的\(1\)少于\(pre\)。代码中的一个细节:把两个独立的方案数乘起来了,因为当其中一个有贡献时,另一个没有贡献,返回的是\(1\),原因就是上面的分讨。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std;
const int Z = 5020; const int mod = 998244353;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }

int n, m, ans;
int fac[Z], inv[Z];
char s[Z];
vector <int> tot[Z];
bitset <Z> c[Z], pre[Z], suf[Z];
inline void init()
{
    fac[0] = 1;
    for (re i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
    inv[n] = qpow(fac[n], mod - 2, mod);
    for (re i = n - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m)
{
    if (n < m || n < 0 || m < 0) return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

sandom main()
{
    n = read(); init();
    for (re i = 1; i <= n; i++)
    {
        scanf("%s", s + 1);
        for (re j = 1; j <= n; j++) c[i][j] = s[j] - '0';
        tot[c[i].count()].push_back(i);
    }
    for (re i = 1; i <= n; i++)//小于等于i的1的状态
    {
        pre[i] = pre[i - 1];
        for (auto j : tot[i]) pre[i] |= c[j];
    }
    for (re i = 1; i <= n; i++) suf[n + 1][i] = 1;
    for (re i = n; i >= 1; i--)//大于等于i的0的状态
    {
        suf[i] = suf[i + 1];
        for (auto j : tot[i]) suf[i] &= c[j];
    }
    for (re i = 0; i <= n; i++)//枚举列上有几个1
        if ((pre[i] & suf[i]) == pre[i] || (pre[i] | suf[i]) == suf[i])//有解(这两个式子是等价的)
        {
            int cnt1 = pre[i].count(), cnt2 = suf[i].count();
            ans += qpow(2, tot[i].size(), mod) * C(cnt2 - cnt1, i - cnt1);//还需要多少个1(非人类写法,实际上是分类讨论)
            ans %= mod;
        }
    cout << ans;
    return 0;
}

T3.团不过

题目背景:眼泪没能流出来。因为已经哭过很多次了。

是雪乃!这我可就不困了,导致我一上午都在搞这道题。首先这道题和\(NIM\)游戏没有半毛钱关系,它叫做卡农。我之所以一上午都在做这个题,是因为用的纯组合数学,3个数组,全部使用容斥计算。这题需要容斥的步骤:异或不为\(0\)转化为总方案减去异或和为\(0\),石子中不能有\(0\),石子间不能重复。第三个容斥因为无法确定状态所以使用纯数学挂了,但题解貌似可以用生成函数硬搞TIP:遇到困难换思路。我们尝试一种新的\(dp\),设\(g[i]\)为前\(i\)个数随便选的总方案数,\(f[i]\)为前\(i\)个数异或和为\(0\)的合法方案数。
转移式子为\(f_i=g_{i-1}-f_{i-1}-(m-i+2)*(i-1) *f_{i-2}\),逐步分析:我们\(g\)的状态囊括了所有的情况,假设前\(i-1\)个数的异或和为\(x\),我要让前\(i\)个数的异或和为\(0\),那么只需要第\(i\)个数选\(x\)就行了,所以一一对应;如果前\(i-1\)的异或和为\(0\),那么第\(i\)个就会选\(0\),不合法;枚举前\(i-1\)个数中哪一个与\(i\)相等,并枚举这个数是啥,有\(m-i+2\)中情况,剩下的\(i-2\)个数异或和为\(0\)。最后输出\(g_n-f_n\)即可。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std;
const int Z = 1e7 + 10; const int mod = 1e9 + 7;
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }

int n, m, f[Z], g[Z];
sandom main()
{
    cin >> n; m = qpow(2, n, mod) - 1;
    g[0] = 1;
    for (re i = 1; i <= n; i++) g[i] = g[i - 1] * (m - i + 1) % mod;//总数
    f[0] = 1, f[1] = 0;
    for (re i = 2; i <= n; i++)//异或和为0的合法方案数
        f[i] = g[i - 1] - f[i - 1] - (m - i + 2) * (i - 1) % mod * f[i - 2] % mod, (f[i] += mod) %= mod;
    cout << (g[n] - f[n] + mod) % mod << endl;//异或和不为0的方案数
    return 0;
}

T4.七负我

题目背景:既然真白你说不要的话,神田君就归我了哦。

赛时因为一直在做T3,所以T4都没有看,然而是只需要求一个图的最大团(最大完全子图)。证明这个结论:
1.对于不直接联通的两个点:(1)间接联通,原贡献是\(ab+bc+cd\),把\(b\)\(d\)合并,得到\(a(b+d)+c(b+d)\),显然更优,同理\(a\)\(c\)也可以合并;(2)不连通,原贡献是\(ab+cd\),把\(c、d\)合并到\(b\)上,得到\(a(b+cd)\),也是更优。对于其他的情况,要么贡献不变,要么更优。所以对于不连通的两个点,我可以把其中一个点的权值移到另一个点上,所以我们最后得到的必是一个完全连通图。
2.团要取最大的,且权值平均分配最优:对于任意一个团,它的贡献是\(\frac{\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}a_ia_j-\sum\limits_{i=1}^{n}a_i^2}{2}\),记作\(ans\),我们发扬基本不等式的光辉,可以得到\(a_ia_j\le \frac{a_i^2+a_j^2}{2}\),当\(a_i=a_j\)时取等,依次化简得到\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}a_ia_j\le n\sum\limits_{i=1}^{n}a_i^2\),当且仅当\(a_1=a_2=…=a_n\)时取等。因为\((\sum\limits_{i=1}^{n}a_i)^2=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}a_ia_j\),所以\(\sum\limits_{i=1}^{n}a_i^2 \ge \frac{1}{n}\sum\limits_{i=1}^{n}a_i\),则\(ans\le \frac{1-\frac{1}{n}}{2}(\sum\limits_{i=1}^{n}a_i)^2\),题目给出\(x=\sum\limits_{i=1}^{n}a_i\),所以\(ans\le \frac{1-\frac{1}{n}}{2}x^2\)\(n\)为团的点数),当团增大时,贡献变大,并且均分才满足取等条件。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 41;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }

int n, m, x, ans;
bitset <Z> dis[Z];
bool dfs(int t, int tot)
{
    if (!tot) return true;
    if (tot > n - t + 1) return false;
    if ((dis[t] & dis[0]) == dis[0])
    {
        dis[0][t] = 1;
        if (dfs(t + 1, tot - 1)) return true;
        dis[0][t] = 0;
    }
    if (dfs(t + 1, tot)) return true;
    return false;
}

sandom main()
{
    n = read(), m = read(), x = read();
    for (re i = 1; i <= m; i++)
    {
        int u = read(), v = read();
        dis[u][v] = dis[v][u] = 1;
    }
    for (re i = n; i; i--)
        if (dfs(1, i)) 
        {
            printf("%.6lf", (1.0 - 1.0 / i) / 2 * x * x);
            return 0;
        }
}

因为题中有Yukino所以放了图。(提示:上面也有)。

posted @ 2022-09-29 21:41  sandom  阅读(61)  评论(2编辑  收藏  举报