CF-Good Bye 2020总结

前言:

这场发挥还行,大号能一发上到1940左右。

E. Apollo versus Pan-数学,按位记贡献,二进制
题目大意:

很粗暴的题目 。给你一个长度为 n n n的数组 a i a_i ai.问你下列求和式的总和

∑ i = 1 n ∑ j = 1 n ∑ k = 1 n ( x i & x j ) ∗ ( x j ∣ x k ) \sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{k=1}^{n}(x_i\&x_j)*(x_j|x_k) i=1nj=1nk=1n(xi&xj)(xjxk)

n ≤ 1 e 5 , a i < 2 60 n \leq 1e5 , a_i < 2^{60} n1e5,ai<260

题目思路:

我原先的化简方法比较繁杂,我都懒得写了,写下题解的做法吧

显然 i , j , k i,j,k i,j,k互相独立,对上式作简单变化得到:

∑ j = 1 n [ ∑ i = 1 n ( x i & x j ) ] p a r t 1 [ ∑ k = 1 n ( x j ∣ x k ) ] p a r t 2 \sum_{j=1}^{n}[\sum_{i=1}^{n}(x_i\&x_j)]_{part1}[\sum_{k=1}^{n}(x_j|x_k)]_{part2} j=1n[i=1n(xi&xj)]part1[k=1n(xjxk)]part2

那么我们可以枚举 j j j。对于确定的 x j x_j xj,分别计算两个部分.

比如第一个部分(另一个部分计算思路类似): ∑ i = 1 n ( x i & x j ) \sum_{i=1}^{n}(x_i\&x_j) i=1n(xi&xj),显然我们可以按位计算贡献.所以再作简单变化得到:

∑ d = 0 60 ∑ i = 1 n ( x i , d & x j , d ) ∗ 2 d \sum_{d=0}^{60}\sum_{i=1}^{n}(x_{i,d}\&x_{j,d})*2^d d=060i=1n(xi,d&xj,d)2d

再根据与运算的特性计算二进制每一位上的贡献:

∑ d = 0 60 [ x j , d = = 1 ] ∗ 2 d ∗ ∑ i = 1 n [ x i , d = = 1 ] \sum_{d=0}^{60}[x_{j,d}==1]*2^d*\sum_{i=1}^{n}[x_{i,d}==1] d=060[xj,d==1]2di=1n[xi,d==1]

f ( i ) , i ∈ [ 0 , 60 ] f(i) , i \in[0,60] f(i),i[0,60] n n n个数中第 i i i位上有多少个 1 1 1.它可以 O ( n l o g max ⁡ ( x i ) ) O(nlog\max(x_i)) O(nlogmax(xi))预处理得到.

∑ d = 0 60 [ x j , d = = 1 ] ∗ 2 d ∗ f ( d ) \sum_{d=0}^{60}[x_{j,d}==1]*2^d*f(d) d=060[xj,d==1]2df(d)

那么现在这个式子和 n n n无关,而是和 d d d有关。可以直接算了.

复杂度分析:

所以对于每一个 j j j.第一个部分我们可以 O ( l o g max ⁡ ( x i ) ) O(log\max(x_i)) O(logmax(xi))求出,第二个部分也是一样.

所以程序总复杂度: O ( n l o g max ⁡ ( x i ) ) O(nlog\max(x_i)) O(nlogmax(xi))

纯推公式题,代码没什么难实现的地方。

F.Euclid’s nightmare-线性基,并查集
题目大意:

给你 n n n个互不相同的 m m m维的 F = { 0 , 1 } F=\{0,1\} F={0,1}的向量。设向量集合为 S S S.它们最多只有 2 2 2个位置上为 1 1 1。你可以对他们两两之间作模 2 2 2意义下的加法运算得到新的向量

现在问你两个问题:
1.假设 T T T为可以由 S S S作模 2 2 2加法导出的向量集合。问你 ∣ T ∣    ( m o d    1 e 9 + 7 ) |T| \ \ (mod \ \ 1e9+7) T  (mod  1e9+7)
2.问 S S S的最小的子集 S ′ S' S使得选择它们就可以得到 T T T.输出 ∣ S ∣ |S| S并给出选择的字典序最小的下标序号.

题目思路:

读完题目发现,这tm不就是线性基的构造思路吗?其实也是向量空间的基的构造思路。只不过模 2 2 2意义下的加法对应到了线性基的构造。所以需要已经理解掌握线性基算法才能够做这题。

第一问很sb。
第二问:
考虑到一个向量最多只有两个 1 1 1.我们可以直接模拟这个过程,但是很容易构造出一组数据将模拟卡到 O ( n 2 ) O(n^2) O(n2).

为什么。因为我们模拟几次线性基构造过程可以发现一个 1 1 1的传递过程可以抽象成一张图。

假设这个1在第 A A A列.那么就找到第 A A A行。
若这一行有另一个1在第 B B B列,那么就再找到第 B B B行。
如此往复…

这个时候我们不难发现,这个过程我们可以用并查集优化行的转化过程。而且也一定是对的。因为每一行最多两个 1 1 1.那么 1 1 1的传递的行数也一定是单向且递增的(递增是因为我们构造的是上三角矩阵).

所以并查集优化以后就可以模拟了。

这里还有一个难想的地方:对于有两个 1 1 1的向量.我们分开考虑两个 1 1 1的贡献. 即我们对两个1的行数分别求一遍并查集然后看是不是有同一个父亲。是的话代表最终这两个 1 1 1会在父亲这一行异或成0.那么这一个向量就没用(异或成0了).

否则,我们看这两个 1 1 1的父亲行数是否已经被之前的向量占领了。对于它们的情况简单讨论一下即可。具体也可以看代码,前面理解了这里也就很简单了.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
ll a[maxn];
int f[maxn] , b[maxn];
ll ksm (ll a , ll b){ ll ans = 1 , base = a;
while (b){if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
int getf (int x) {return x == f[x] ? x : f[x] = getf(f[x]);}
int main()
{
    ios::sync_with_stdio(false);
    int n , m; cin >> n >> m;
    for (int i = 1 ; i <= m ; i++){
        f[i] = i;
        b[i] = 0;
    }
    vector<int> res;
    for (int i = 1 ; i <= n ; i++){
        int k;cin >> k;
        if (k == 1){
            int x; cin >> x;
            int fx = getf(x);
            if (b[fx]) continue;
            b[fx] = 1;
            res.push_back(i);
        }else {
            int x , y; cin >> x >> y;
            int fx = getf(x) , fy = getf(y);
            if (fx == fy) continue;
            if (!b[min(fx , fy)]){
                b[min(fx , fy)] = 1;
                f[min(fx,fy)] = max(fx,fy);
                res.push_back(i);
                continue;
            }
            if (!b[max(fx , fy)]) {
                b[max(fx , fy)] = 1;
                res.push_back(i);
                continue;
            }
        }
    }
    int cnt = 0;
    for (int i = 1 ; i <= m ; i++){
        cnt += b[i];
    }
    cout << ksm(2 , cnt) << " " << res.size() << endl;
    for (auto g : res) cout << g << " ";
    cout << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值