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\),所以可以快速查询:
- 是否能被这组数的异或和表示
- 异或和最大最小\(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;
}