Codeforces Round #779 (Div. 2)简训
导语
两道,菜
涉及的知识点
数学,位运算,思维
链接: Codeforces Round #779 (Div. 2)
题目
A Marin and Photoshoot
题目大意:给出一个01串,要求任何一个连续的长度大于2的区间内1的个数大于等于0的个数,判断需要插入的最小字符个数
思路:贪心的考虑,直接对每个0之间的长度,如果小于3则直接插入1,特判整个串为1的情况
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
string s;
cin >>s;
if(n==1) {//特判长度为1
cout <<0<<endl;
continue;
}
vector<int>pos;
int len=s.length(),ans=0;
for(int i=0; i<len; i++)//收录所有0的位置
if(s[i]=='0')pos.push_back(i);
if(pos.size())
for(int i=0; i<pos.size()-1; i++)//获得需要插入的个数
if(pos[i+1]-pos[i]<=2)ans+=3-pos[i+1]+pos[i];
cout <<ans<<endl;
}
return 0;
}
B Marin and Anti-coprime Permutation
题目大意:定义一个好排列满足: g c d ( 1 × p 1 , 2 × p 2 , … , n × p n ) > 1 gcd(1×p_1,2×p_2,\dots,n×p_n)>1 gcd(1×p1,2×p2,…,n×pn)>1,给出 n n n,判断 1 1 1到 n n n的排列中有多少个好排列
思路:打表可以得到,奇数结果为0,偶数为 n / 2 n/2 n/2之间的平方积
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
const int mod=998244353;
int t,n;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
if(n&1) {//奇数都是0
cout <<0<<endl;
continue;
}
int res=1;
for(int i=2;i<=n;i+=2)//平方的积
res=((res*i/2)%mod*i/2)%mod;
cout <<res<<endl;
}
return 0;
}
C Shinju and the Lost Permutation
题目大意:给出一个初始排列 p p p,定义第 i i i次循环操作后的结果使得 [ p 1 , p 2 , … , p n ] [p_1,p_2,\dots,p_n] [p1,p2,…,pn]变成 [ p n − i + 1 , … , p n , p 1 , p 2 , … , p n − i ] [p_{n-i+1},\dots,p_n,p_1,p_2,\dots,p_{n-i}] [pn−i+1,…,pn,p1,p2,…,pn−i],定义 p p p序列的值为不同长度前缀下的最大值,例如对于序列 [ 1 , 2 , 5 , 4 , 6 , 3 ] [1,2,5,4,6,3] [1,2,5,4,6,3],可以得到一个 b b b序列: [ 1 , 2 , 5 , 5 , 6 , 6 ] [1,2,5,5,6,6] [1,2,5,5,6,6],进行一次循环操作后可以得到一个新的 b b b序列: [ 2 , 5 , 5 , 6 , 6 , 6 ] [2,5,5,6,6,6] [2,5,5,6,6,6],现在对于每个 b b b序列,存在一个数 c c c,那么 n n n次操作就有一个序列 c c c, c c c是每一次操作后得到的 b b b序列中数字的种类个数例如前面例子就可以得到 c c c的一部分: [ 4 , 3 ] [4,3] [4,3],现在给出 c c c序列,判断是否存在原序列 p p p能够满足 c c c序列
思路:题意较难理解,理清楚之后思路可能会好很多,首先对于
c
c
c序列,有且只有一个1,因为一定存在一次操作,使得序列最大值作为
p
1
p_1
p1,那么构造出来的
b
b
b就会全是最大值,判断给出的
c
c
c如果1的个数不为1,那么一定无法构造
那么,把
c
c
c为1的位置作为
c
1
c_1
c1进行直接调换,这样就可以得到每次操作之后
c
i
c_i
ci的变化了,讨论相邻两项
c
i
,
c
i
+
1
c_i,c_{i+1}
ci,ci+1的情况,如果
c
i
+
1
−
c
i
>
1
c_{i+1}-c_i>1
ci+1−ci>1,代表从后面调到前面一个数使得p中最长递增子序列的长度增加了2,这显然是不可能的,因为最多只会增加1,如果
c
i
+
1
−
c
i
=
1
c_{i+1}-c_i=1
ci+1−ci=1,代表增加1,这是可以构造的,如果
c
i
+
1
−
c
i
=
0
c_{i+1}-c_i=0
ci+1−ci=0,代表不变,这也是可以构造的,相当于
p
n
p_n
pn代替了最长递增子序列中的最小项,如果
c
i
+
1
−
c
i
<
0
c_{i+1}-c_i<0
ci+1−ci<0,也可以构造,代表代替了最长递增子序列中的前
c
i
−
c
i
+
1
c_i-c_{i+1}
ci−ci+1项,判断
c
i
c_i
ci和
c
i
+
1
c_{i+1}
ci+1大小关系即可
代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=1e5+50;
int t,n;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
vector<int>c(n);
for(int &x:c)cin >>x;
if(count(c.begin(),c.end(),1)!=1) {
//如果有超过一个位置有1,或者没有1的位置则一定是错的
cout <<"NO\n";
continue ;
}
int pos=find(c.begin(),c.end(),1)-c.begin();
//找到1的位置
rotate(c.begin(),c.begin()+pos,c.end());
//以1为边界,交换两个区间,把1换到前面来
bool flag=1;
for(int i=1; i<n; ++i)
if(c[i]-c[i-1]>1) {//判断是否存在一个大于2
cout <<"NO\n";
flag=0;
break;
}
if(flag)cout <<"YES\n";
}
return 0;
}
D1 388535 (Easy Version)
题目大意:给出两个整数 l , r l,r l,r,给出一个长度为 r − l + 1 r-l+1 r−l+1的排列: [ l , l + 1 , … , r ] [l,l+1,\dots,r] [l,l+1,…,r],对每个数异或一个 x x x,给出异或之后的结果,求 x x x,保证l=0
思路:由于保证了左边界为0,那么可以保证异或一个数之后与之前每一位上0与1的和为定值,因此可以统计异或前与异或后的每一位上的01,出现变化则代表x这一位为1了
当然Hard难度下两个代码也可以过,毕竟是更一般的情况
代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=2e5+5;
int t,l,r,cnt[20],res[20];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>l>>r;
int ans=0;
memset(cnt,0,sizeof(cnt));
memset(res,0,sizeof(res));
for(int i=l; i<=r; i++) {
int x;
cin >>x;
for(int j=0; j<=17; j++)//提取初始序列每一位上的1
if(i>>j&1)cnt[j]++;
for(int j=0; j<=17; j++)//提取结果序列每一位上的1
if(x>>j&1)res[j]++;
}
for(int j=0; j<=17; j++)//判断1的个数是否有变化
if(res[j]==(r-l+1)-cnt[j])ans|=(1<<j);
cout <<ans<<endl;
}
return 0;
}
D2 388535 (Hard Version)
题目大意:给出两个整数 l , r l,r l,r,给出一个长度为 r − l + 1 r-l+1 r−l+1的排列: [ l , l + 1 , … , r ] [l,l+1,\dots,r] [l,l+1,…,r],对每个数异或一个 x x x,给出异或之后的结果,求 x x x,保证l≥0
思路:有两种思路,一种是通过找规律得到的,一种是通过字典树
异或规律:对于连续区间中的一对相邻数字 2 p , 2 p + 1 2p,2p+1 2p,2p+1有这样的一个规律: 2 p ⊕ 2 p + 1 = 1 2p⊕2p+1=1 2p⊕2p+1=1,那么可以将给定区间内的数字两两配对获得定值,匹配会出现下列几种情况
- l l l奇, r r r奇,如 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5,匹配完后会剩下 l ⊕ x l⊕x l⊕x,那么与 l l l异或后得到 x x x,具体做法是去掉所有的匹配,这样剩下来的就是 l ⊕ x l⊕x l⊕x了
- l l l奇, r r r偶, l ⊕ x , r ⊕ x l⊕x,r⊕x l⊕x,r⊕x都无法匹配,两个值都尝试一下
- l l l偶, r r r奇,则都可以匹配,则 x x x的末尾可0可1(尝试异或0或1只是结果顺序不同),将所有的数右移一位,重复匹配
- l l l偶, r r r偶,和情况1类似
字典树:对于异或问题,首先由二进制来考虑,设异或的结果为数组
b
b
b,原数组为
a
a
a,暴力的思路是枚举一个
x
x
x来求异或最大值或者最小值,并且分别判断是否与
r
,
l
r,l
r,l相等,为什么要这样判断?显然,
b
b
b中的数字各不相同,根据异或的性质,每个数异或上
x
x
x之后,还原的序列里的数字也各不相同,所以只需要满足异或的最大值与最小值对应相等即可
暴力枚举值域内的
x
x
x显然不行,这个时候可以利用异或的性质,枚举每个
b
i
⊕
l
b_i⊕l
bi⊕l,因为
b
i
=
a
i
⊕
x
b_i=a_i⊕x
bi=ai⊕x,
b
i
b_i
bi一定有一个
l
⊕
x
l⊕x
l⊕x,最后会剩下
x
x
x,异或式子为
a
i
⊕
x
⊕
b
i
⊕
l
=
l
a_i⊕x⊕b_i⊕l=l
ai⊕x⊕bi⊕l=l
代码(异或规律)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,l,r;
set<int>s,tmp;
bool judge(int x) {
for(auto i:s)
if((i^x)<l||(i^x)>r)return 0;
return 1;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>l>>r;
s.clear();
for(int i=l; i<=r; i++) {//保存所有的数字
int x;
cin >>x;
s.insert(x);
}
int ans=1;
while(!s.empty()) {
tmp=s;
for(auto i:s)//去掉
tmp.erase(i^1);
if((l&1)&(r&1)) {//都是奇数
ans*=(*tmp.begin()^l);
break;
}
if((l&1)&!(r&1)) {//奇和偶
if(judge(*tmp.begin()^l))//暴力判断
ans*=(*tmp.begin()^l);
else
ans*=(*tmp.begin()^r);
break;
}
if(!(l&1)&!(r&1)) {//如果都是偶
ans*=(*tmp.begin()^r);
break;
}
//偶和奇,最后一位可0可1,按照0解决
tmp.clear();
for(auto i:s)
tmp.insert(i>>1);
s=tmp;
l>>=1,r>>=1,ans<<=1;
}
cout <<ans<<endl;
}
return 0;
}
代码(字典树)
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=5e5+50;
int t,l,r,trie[maxn][2],tot,a[maxn];
void Insert(int x) {
int p=1;
for(int i=17; i>=0; i--) {
bool k=x&(1<<i);
if(!trie[p][k]) {
trie[p][k]=++tot;
trie[tot][0]=trie[tot][1]=0;//初始化,防止先前的记录干扰
}
p=trie[p][k];
}
}
int GetMax(int x) {
int p=1,res=0;
for(int i=17; i>=0; i--) {
bool k=x&(1<<i);
if(trie[p][k^1]) {//走反路径,这样才能得到最大的异或值
res+=1<<i;
p=trie[p][k^1];
} else
p=trie[p][k];//如果没有就只能走原数字,因为走到根才是完整的数字
}
return res;
}
int GetMin(int x) {
int p=1,res=0;
for(int i=17; i>=0; i--) {
bool k=x&(1<<i);
if(trie[p][k])//走当前路径,获得最小异或值
p=trie[p][k];
else {
res+=1<<i;//给这一个赋值
p=trie[p][k^1];
}
}
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>l>>r;
tot=1;
trie[1][0]=trie[1][1]=0;//注意初始化,全部清零会超时
for(int i=1; i<=r-l+1; i++) {
cin >>a[i];
Insert(a[i]);
}
for(int i=1; i<=r-l+1; i++)
if(GetMax(a[i]^l)==r&&GetMin(a[i]^l)==l) {//判断最大值和最小值
cout <<(a[i]^l)<<endl;
break;
}
}
return 0;
}