Codeforces Round #473 (Div. 2) 题解

Codeforces Round #473 (Div. 2)

A、B、C略,C题感觉还挺有意思的……

Codeforces 959D

题意

给定一个数列\(\{a\}\),输出一个字典序大于等于\(\{a\}\)的数列\(\{b\}\),并且\(\{b\}\)中任意两个数互质

\(2\le a_i \le 1e5, 1 \le n \le 1e5\)\(n\)为数列长度

解题思路

贪心,创建一个\([2,MAXANS]\)的集合

每次\(\{b\}\)加入集合中大于等于\(a_i\)的最小的数,然后用类似筛法的方法划掉所有和\(a_i\)不互质的数

如果这个过程中已经保证了\(\{b\}\)的字典序大于\(\{a\}\),那么每次加入改成集合中最小的数

实现上首先用筛法对\([2,MAXANS]\)的数进行质因数分解存进vector

\(2 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 19=9699690\),所以数据范围内每个数质因子个数不超过\(8\)

然后用set实现这个集合,这样每个数最多被删一次,查询是否被删时最多被访问它的质因子个数次,所以时间复杂度为\(O(MAXANS( \log MAXANS + 8))\)

经试验得\(MAXANS \le 2e6\)

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+7;
int a[maxn],b[maxn],n;
set <int> s;
vector <int> fac[maxn];
bool vis[maxn],pvis[maxn];
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=2;i<maxn;i++)
    {
        s.insert(i);
        if(!pvis[i])
            for (int j=i;j<maxn;j+=i)
            {
                fac[j].push_back(i);
                pvis[j]=true;
            }
    }
    bool great=false;
    for (int i=1;i<=n;i++)
    {
        if(!great)
            b[i]=*s.lower_bound(a[i]);
        else
            b[i]=*s.begin();

        if(b[i]>a[i])   great=true;

        for (int j: fac[b[i]])
        {
            for (int x=j;x<maxn;x+=j)
                if(!vis[x])
                {
                    s.erase(x);
                    vis[x]=true;
                }
        }
    }

    for (int i=1;i<=n;i++)
        printf("%d ",b[i]);
    puts("");
}

Codeforces 959E

题意

给定\(n\)个点的完全图,点权为\(0\)\(n-1\),边权为两顶点点权的异或值,求最小生成树的权值和

$ 2 \le n \le 1e12$

解题思路

kruscal打表(然后比赛时候建图建错了,淦)

发现两个数差的规律是:每隔2个数有个2,每隔4个数有个4(覆盖掉前面的2),每隔8个数有个8(覆盖)……,其余全是1

(其实是\(lowbit(i-1)\)

预处理\(2\)的幂,一层一层加即可

AC代码

#include <bits/stdc++.h>
using namespace std;
int main()
{
    long long p[60],n;
    cin>>n;
    p[0]=0;
    for (int i=1;i<=60;i++)
        p[i]=(1ll<<(i-1));
    n=n-1ll;
    long long res=0,t=1;
    while(n)
    {
        res+=(p[t]-p[t-1])*n;
        n>>=1ll;
        t++;
    }
    cout<<res<<endl;
}

Codeforces 959F

题意

给定数列\(\{a\}\),长度\(n\)\(q\)个询问,询问包含\(x\)\(l\),输出\(\{a\}\)的前缀\(\{a_1,\cdots,a_l\}\)有多少个子列异或和为\(x\)

$ 1 \le n,q \le 1e5, 0 \le a_i ,x \le 2e20$

解题思路

需要用到一个东西叫做线性基

线性基

如果将每个数看成\(\{0,1\}\)向量,组成的向量做异或运算其实就是在模\(2\)意义上的加法运算

如果求出所有数的基底,那么这组基就能够异或和出所有的异或和(把做异或运算的每个数都用基底表示,由于是模\(2\)意义下的,系数整理后模\(2\)要么是\(0\),要么是\(1\),和直接异或和等价)

而且在long long范围内这个极大线性无关组的大小至多是\(64\),所以可以快速查询:

  1. 是否能被这组数的异或和表示
  2. 异或和最大最小\(k\)小值

对于这道题:

求出每个前缀的线性基,查询是否能被表示,如果不能被表示,答案是0,否则答案是\(2^{l-|B_l|}\)\(|B_l|\)是线性基的大小,这是因为线性基之外的数可以随便选,即他们的异或和是\(S\),那么只要用线性基表示出\(x\ xor\ S\)即可

线性基的完整说明和实现可以参考:这个这个,和这个

考虑数据范围线性基最大位数做成\(21\)就够用了

时间复杂度\(O(21 \times (n+q))\)

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
const long long mod=1e9+7;
int bas[maxn][22],cnt[maxn],a[maxn];
long long p[maxn];
int n,q;
bool check(int l,int x)
{
    for (int i=20;i>=0;i--)
    {
        if((x>>i)&1)
        {
            if(!bas[l][i]) return 0;
            x=x^bas[l][i];
        }
    }
    return x==0;
}
int main()
{
    scanf("%d%d",&n,&q);
    for (int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=n;i++)
    {
        for (int j=20;j>=0;j--) bas[i][j]=bas[i-1][j];
        cnt[i]=cnt[i-1];
        for (int j=20;j>=0;j--)
        {
            if((a[i]>>j)&1)
            {
                if(!bas[i][j]) {bas[i][j]=a[i];cnt[i]++;break;}
                a[i]^=bas[i][j];
            }
        }
    }
    p[0]=1ll;
    for (int i=1;i<maxn;i++)    p[i]=(p[i-1]<<1)%mod;
    while(q--)
    {
        int l,x;
        scanf("%d%d",&l,&x);
        if(!check(l,x))  puts("0");
        else printf("%I64d\n",p[l-cnt[l]]);
    }
    return 0;
}
posted @ 2018-04-09 00:40  阿瓦隆的精灵  阅读(321)  评论(0编辑  收藏  举报