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]=1g[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 (二分图)

给出 nn 大小的网格,每一个最大值 bi ,每一列最大值 ci ,你需要对一些点赋值,使得总的赋值和最小


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

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

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

[v1[now].size()+v2[now].size()maxpipei]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)

x2+y2xy+1=k(k=1,2,...)1xyn

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

对任意 k>=2,a[i]=pow(k,2)a[i1]a[i2] ,其中 a[0]=0,a[1]=k

那么我们只要枚举其中较大的那个数,打表后二分即可,由于a[2]=k3,因此我们只需要枚举到 1e6 ,时间复杂度 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,x3) 都符合条件。

接下来,设 k(xy+1)=x2+y2 ,此时固定 x 整理方程为 y2kxyk+x2=0

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

由韦达定理(伟大定理)y1+y2=kx

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

此时再回头看 (x,x3) 代入

(x,x3) 此时 k=x2

(x3,x5x)

...

无限套娃下去。(有点类似于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 @   RioTian  阅读(153)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 全程不用写代码,我用AI程序员写了一个飞机大战
历史上的今天:
2020-07-25 有向无环图
2020-07-25 图的存储
2020-07-25 Codeforces Round #658 (Div. 2)
2020-07-25 POJ 3259 Wormholes(bellman_ford、Floyd、SPFA判断负环)
2020-07-25 C++ string 字符串函数详解
点击右上角即可分享
微信分享提示

📖目录