2021牛客暑期多校训练营3 个人补题记录

比赛链接:Here

1001 - Guess and lies

1002 - Black and white (Kruskal & 并查集)

为了实现最少花费,需满足:在我们涂完若干个点后,其他的点对总花费不再有贡献(涂黑这些点时不花钱了)
我们会发现 :最少,我们需要涂黑 n + m - 1 个点,且涂完这些点后 所有的行 和所有的列 会都在一个联通块里面

举例 如下:
不妨令 n = 2,m = 2,此时我们最少需要涂 3个点
\(g[1][1] = g[1][2] = g[2][1] = 1,g[2][2] = 3\)
为了最少花费,我们要涂的三个点显然是:(1,1),(1,2),(2,1)
涂完(1,1)后,我们把 行1 和 列1 放到一个联通块里面
涂完(1,2)后,我们把 行1 和 列2 放到一个联通块里面
涂完(2,1)后,我们把 行2 和 列1 放到一个联通块里面
到此,所有的行 和 所有的列 都在一个联通块里了,同时我们也实现了最少花费

在这个例子中,涂黑任意三个点都可以实现 把所有的行 和 所有的列 都放在一个联通块里
但为了 最少花费,所以我们选择的是 \((1,1),(1,2),(2,1)\) 这三个点
我们提炼出两个关键字 联通块、最小花费:连通块(并查集)+ 最小花费 = Kruskal(最小生成树)

然后发现直接根据条件写一个并查集优化更好一点

const int N = 1e5 + 10;
int f[N];
vector<pair<int, int>> v[N];
int find(int x) {return f[x] == x ? x : f[x] = find(f[x]);}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, m;
    ll a, b, c, d, p, ans = 0;
    cin >> n >> m >> a >> b >> c >> d >> p;
    for (int i = 1; i <= n + m; ++i) f[i] = i;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            a = (a * a * b + a * c + d) % p;
            v[a].push_back(make_pair(i, j + n));
        }
    for (int i = 0; i <= p; ++i)
        for (auto [x, y] : v[i]) {
            int xx = find(x);
            int yy = find(y);
            if (xx != yy) {
                ans += i;
                f[xx] = yy;
            }
        }
    cout << ans;
}

1003 - Minimum grid (二分图)

给出 \(n*n\) 大小的网格,每一个最大值 \(b_i\) ,每一列最大值 \(c_i\) ,你需要对一些点赋值,使得总的赋值和最小


将每个数值对应的行,以及每个数值对应的列分别进行存储。枚举每一个数值,范围 \([1,k]\)

枚举当前数值所对应的行和列,如果当前行列可以填数,则将行和列建边,求出行和列的最大匹配数。

当前数值的贡献为:(当前值对应的行数+当前值对应列数-最大匹配数)*当前值。
使用 vector 进行存储,所对应的值就是:

\[[v1[now].size() + v2[now].size() - max_{pipei}] * now,(now为当前值) \]

此处的最大匹配值可以理解为当前行和列的最大值均已经满足,可以填 0 的最大数量。

const int N = 1e4 + 10, M = 1e6 + 10, inf = 1e8;
int n, m, k, x;
int mp[N][N];
vector<int>G1[M], G2[M];
vector<int>G[N];
int st[N], match[N];

bool dfs(int u) {
    for (auto v : G[u]) {
        if (st[v]) continue;
        st[v] = 1;
        if (match[v] == -1 || dfs(match[v])) {
            match[v] = u;
            return 1;
        }
    }
    return 0;
}
int cal() {
    memset(match, -1, sizeof(match));
    int res = 0;
    for (int i = 1; i <= n + n; ++i) {
        memset(st, 0, sizeof(st));
        if (dfs(i))res++;
    }
    return res;
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i) cin >> x, G1[x].push_back(i);
    for (int i = 1; i <= n; ++i) cin >> x, G2[x].push_back(i + n);
    while (m--) {
        int x, y;
        cin >> x >> y;
        mp[x][y] = 1;
    }
    ll ans = 0 ;
    for (int now = k; now >= 1; --now) {
        if (G1[now].size() == 0 && G2[now].size() == 0) continue;
        for (int i = 1; i <= n + n; i++) G[i].clear();
        for (auto i : G1[now])
            for (auto j : G2[now])
                if (mp[i][j - n]) G[i].push_back(j), G[j].push_back(i);
        // 建图
        ll maxpipei = cal() / 2;
        ll maxm = G1[now].size() + G2[now].size() - maxpipei;
        ans = ans + maxm * now;
    }
    cout << ans << '\n';
}

二分图相关题:Here

没看出这道题是二分图太丢脸了(别骂了,别骂了)

1004 - Count

1005 - Math (签到?)

比赛时,我擦这公式咋推的啊,这么多人过的???

赛后很好奇为咩能出现 IMO 的题啊!!

https://zhuanlan.zhihu.com/p/32815892

最后靠的打表找规律了... (打表yyds)

\[\frac{x^2+y^2}{xy + 1} = k (k=1,2,...)\in 1\le x\le y\le n \]

经过一系列的复杂计算及推演最终得出一个结论:任何x,y都会满足一个式子:

对任意 \(k>=2,a[i] = pow(k,2) * a[i-1] - a[i-2]\) ,其中 \(a[0]=0,a[1]=k\)

那么我们只要枚举其中较大的那个数,打表后二分即可,由于\(a[2]=k^3\),因此我们只需要枚举到 \(1e6\) ,时间复杂度 \(\mathcal{O}(1e6 + Tlog\ n)\)

const int N = 1e6  + 10;
vector<ll>a;
ll b[N];
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    for (int k = 2; k <= 1e6; ++k) {
        b[0] = 0, b[1] = k;
        for (int i = 2; i <= 60; ++i) {
            b[i] = pow(k, 2) * b[i - 1] - b[i - 2];
            if (b[i] > 1e18 || b[i] < 0)break;
            a.push_back(b[i]);
        }
    }
    sort(a.begin(), a.end());
    a.erase(unique(a.begin(), a.end()), a.end());
    int _; for (cin >> _; _--;) {
        ll n;
        cin >> n;
        cout << upper_bound(a.begin(), a.end(), n) - a.begin() + 1 << "\n";
    }
}

贴一下 nagisa_菜鸡桑的思路和推导过程

这道题一拿到手应该大部分人和我一样想到打表。打出一部分表后,我们发现,任意数 \((x,x^3)\) 都符合条件。

接下来,设 \(k(xy + 1) = x^2 + y ^2\) ,此时固定 \(x\) 整理方程为 \(y^2 - kxy - k + x^2 = 0\)

之所以固定 \(x\) 是为了接下来便于枚举 \(x\)

由韦达定理(伟大定理)\(y_1 + y_2 = kx\)

  • 我们现在知道 \((x,y)\) 符合条件,那么也就说明 \((y,x)\) 也成立,那么我们可以通过 \((x,kx - y)\) 找出以 \(y\) 作为第一个元素的数对,即 \((y,一个数)\)
  • 我们现在知道 \((x,y)\) 符合条件,因此 一个数 = \(ky - x\) ,即我们得到的新的数对为 \((y,ky - x)\)

此时再回头看 \((x,x^3)\) 代入

\((x,x^3)\) 此时 \(k=x^2\)

\((x^3,x^5-x)\)

...

无限套娃下去。(有点类似于gcd)
之后就是把打出来的第二元素放进数组里,排序然后二分就行了。

1006 - 24dian

为咩我都没看出规律

等巨佬给我讲一下思路把

// 待补

1007 - Yu Ling(Ling YueZheng) and Colorful Tree

1008 - Ling Qiu, Luna and Triple Backpack

1009 - Kuriyama Mirai and Exclusive Or (树状数组维护差分)

这里是一个调了超级久的树状数组发现错了的蒟蒻(菜鸡

正解是用树状数组维护写一个差分数组

const int N = 6e5 + 10;
int n, q, a[N], aa[N];
bool b[N][30];

void update(int l, int x) {
    for (int i = 0, j = 1; i < 30; ++i, j <<= 1) {
        if (x & j and l <= n) aa[l] ^= j;
        int k = (((x >> i) + 1) << i) - x + l;
        if (k <= n) b[k][i] ^= 1;
    }
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin >> n >> q;
    for (int i = 1; i <= n; ++i )cin >> a[i];
    while (q--) {
        int op, l, r, x;
        cin >> op >> l >> r >> x;
        if (op) {
            update(l, x);
            update(r + 1, x + r - l + 1);
        } else
            aa[l] ^= x, aa[r + 1] ^= x;
    }
    for (int i = 0, k = 1; i < 30; i++, k <<= 1) {
        for (int j = 1; j <= n; ++j) {
            if (j + k <= n)
                b[j + k][i] ^= b[j][i];
            if (b[j][i])
                aa[j] ^= k;
        }
    }
    for (int i = 1; i <= n; ++i) {
        aa[i] ^= aa[i - 1];
        cout << (a[i] ^ aa[i]) << " \n"[i == n];
    }
}

1010 - Counting Triangles (签到?)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
namespace GenHelper {
unsigned z1, z2, z3, z4, b, u;
unsigned get() {
    b = ((z1 << 6)^z1) >> 13;
    z1 = ((z1 & 4294967294U) << 18)^b;
    b = ((z2 << 2)^z2) >> 27;
    z2 = ((z2 & 4294967288U) << 2)^b;
    b = ((z3 << 13)^z3) >> 21;
    z3 = ((z3 & 4294967280U) << 7)^b;
    b = ((z4 << 3)^z4) >> 12;
    z4 = ((z4 & 4294967168U) << 13)^b;
    return (z1 ^ z2 ^ z3 ^ z4);
}
bool read() {
    while (!u) u = get();
    bool res = u & 1;
    u >>= 1; return res;
}
void srand(LL x) {
    z1 = x;
    z2 = (~x) ^ 0x233333333U;
    z3 = x ^ 0x1234598766U;
    z4 = (~x) + 51;
    u = 0;
}
}
using namespace GenHelper;
bool edge[8005][8005];
LL a[8005];
int main() {
    LL n, seed;
    cin >> n >> seed;
    srand(seed);
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++) {
            edge[j][i] = edge[i][j] = read();
            if (edge[i][j])  a[i]++, a[j]++;
        }
    LL ans = 0;
    for (int i = 0; i < n; i++)
        ans += a[i] * (n - a[i] - 1);
    cout << n * (n - 1) * (n - 2) / 6 - ans / 2;
}
posted @ 2021-07-25 15:12  RioTian  阅读(148)  评论(0编辑  收藏  举报