冷知识,“小白”月赛的Rating还是蛮高的。比赛链接
A-F主要是推结论,找到结论之后就简单了,不难。G题很难,做法精妙。
A 小苯的石子游戏
思路:
两人肯定贪心的选取当前最大的石子堆,对石子堆的大小从大到小排序,那么Alice肯定会拿到第 1 , 3 , 5 , … , 2 k − 1 1,3,5,\dots,2k-1 1,3,5,…,2k−1 堆石子堆,Bob肯定会拿到第 2 , 4 , 6 , … , 2 k 2,4,6,\dots,2k 2,4,6,…,2k 堆石子堆,累计一下然后比较就行了。
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=105;
int T,n;
int a[maxn];
int main(){
cin>>T;
while(T--){
cin>>n;
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=n;i>=1;i-=2)
cnt1+=a[i];
for(int i=n-1;i>=1;i-=2)
cnt2+=a[i];
puts((cnt1>cnt2)?"Alice":"Bob");
}
return 0;
}
B 小苯的排序疑惑
思路:
首先这个区间不能全选,其次最多选一次。既然不管怎么选都只算作一次操作,那么我们肯定选 [ 2 , n ] [2,n] [2,n] 或者 [ 1 , n − 1 ] [1,n-1] [1,n−1] 这两者之一。
如果要保证选出区间排序后整段区间都有序,如果我们选的 [ 2 , n ] [2,n] [2,n] ,那么第一个元素就必须本来就在正确的位置上,同理 [ 1 , n − 1 ] [1,n-1] [1,n−1]。
所以我们看一下第一个元素是不是最小的,或者最后一个元素是不是最大的,两者满足一个就是YES
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
const int inf=2e9;
int T,n;
int a[maxn];
int main(){
cin>>T;
while(T--){
cin>>n;
int maxx=-inf,minn=inf;
for(int i=1;i<=n;i++){
cin>>a[i];
maxx=max(maxx,a[i]);
minn=min(minn,a[i]);
}
if(a[1]==minn || a[n]==maxx)puts("YES");
else puts("NO");
}
return 0;
}
C,D 小苯的IDE括号问题
思路:
只涉及添加删除一个元素,以及光标向前后移动一位,显然链表题。光标 I
可以看作是链表上的一个特殊指针。模拟一下操作过程,然后顺序输出链表内容就可以了。没什么思维难度,就是难写。
code:
这里链表头节点默认是
0
0
0 号节点,维护的是双向循环链表,每次在末尾插入元素相当于向头节点之前插入元素。it 指针表示光标i
。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=2e5+5;
int n,k;
int pre[maxn],nxt[maxn],val[maxn],cnt,it,p;
string str;
void del(int p){//删除p节点
nxt[pre[p]]=nxt[p];
pre[nxt[p]]=pre[p];
}
int main(){
cin>>n>>k>>str;
for(int i=0;i<n;i++){
if(str[i]=='(' || str[i]==')'){
val[++cnt]=(str[i]==')');
pre[cnt]=pre[0];
nxt[cnt]=nxt[pre[0]];
nxt[pre[0]]=cnt;
pre[0]=cnt;
}
else it=pre[0];
}
for(int i=1;i<=k;i++){
cin>>str;
if(str=="backspace"){
if(it){
p=pre[it];
if(val[it]==0 && nxt[it] && val[nxt[it]]==1){
del(it);
it=nxt[it];
}
del(it);
it=p;
}
}
else if(str=="delete"){
if(nxt[it]){
p=nxt[it];
del(p);
}
}
else if(str=="<-"){
if(it)it=pre[it];
}
else if(str=="->"){
if(nxt[it])it=nxt[it];
}
}
if(it==0)printf("I");
for(int p=nxt[0];p;p=nxt[p]){
printf("%c",(val[p])?')':'(');
if(p==it)printf("I");
}
return 0;
}
E 小苯的数组构造
思路:
如果我们取出数组 a a a 的最大元素,把所有元素都变成它,最多加 2 ∗ 1 0 9 2*10^9 2∗109,不会超过数组 b b b 的数据范围,所以这个题是一定有解的。
考虑怎么把一对逆序对变成非逆的。只给大数减,给大数减小数加 其实和 只给小数加 得到的极差是一样的,所以我们可以只考虑给小数加到和大数相同,这个时候数组 b b b 就一定会有大数-小数的差值存在,极差至少会是这个值。
那么问题就变成了找到差值最大的逆序对,它的差值其实就是数组 b b b 的极差。而第 i i i 个元素如果小于前 i − 1 i-1 i−1 个最大的那个元素,就变成了那个元素, b b b 数组就是增加值。跑一遍就可以得到 b b b 数组了。
code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
const int inf=2e9;
int n;
int main(){
cin>>n;
int mx=-inf;
for(int i=1,t;i<=n;i++){
cin>>t;
if(t>=mx){
mx=t;
printf("0 ");
}
else printf("%d ",mx-t);
}
return 0;
}
F 小苯的数组切分
思路:
乍一看没什么思路,但是手玩一下会发现一个规律。那就是最后一段一定只包含最后一个元素。
因为与是越与越小的,而或是越或越大的。所以你与其给与,不如给旁边的或。我们把最后一个元素直接给与,然后枚举前面两个区间的断点,记录最大值。区间异或的结果可以用前缀和处理,区间或的结果可以用后缀和处理,最后枚举断点记录最大值。就做完了。
code:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
ll n,a[maxn],ans;
ll suf[maxn],pre[maxn];//后缀或,前缀异或
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
n--;
pre[1]=a[1];
suf[n]=a[n];
for(int i=2;i<=n;i++)
pre[i]=pre[i-1]^a[i];
for(int i=n-1;i>=1;i--)
suf[i]=suf[i+1]|a[i];
for(int i=1;i<n;i++)
ans=max(ans,pre[i]+suf[i+1]);
cout<<ans+a[n+1]<<endl;
return 0;
}
G 小苯的逆序对
思路:
这题好啊,官方视频讲解
我们假设
d
p
[
d
]
dp[d]
dp[d] 表示
g
c
d
(
a
i
,
a
j
)
=
d
gcd(a_i,a_j)=d
gcd(ai,aj)=d 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数。直接求不好求,但是我们大概好求
g
c
d
(
a
i
,
a
j
)
=
k
d
gcd(a_i,a_j)=kd
gcd(ai,aj)=kd 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数。我们把所有
d
d
d 的倍数的
a
i
a_i
ai 都提取出来,单独求逆序对,得到的就是既是逆序对,又都至少包含因子
d
d
d 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数。算出来的就是
g
c
d
(
a
i
,
a
j
)
=
k
d
gcd(a_i,a_j)=kd
gcd(ai,aj)=kd 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数。
考虑怎么把
g
c
d
(
a
i
,
a
j
)
=
k
d
gcd(a_i,a_j)=kd
gcd(ai,aj)=kd 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数 转化成
g
c
d
(
a
i
,
a
j
)
=
d
gcd(a_i,a_j)=d
gcd(ai,aj)=d 的逆序对
(
i
,
j
)
(i,j)
(i,j) 对的对数。实际上假设
d
∼
n
d\sim n
d∼n 的
d
p
dp
dp 值我们都已经算出来了,那么其实
[
g
c
d
(
a
i
,
a
j
)
=
d
]
的逆序对对数
=
[
g
c
d
(
a
i
,
a
j
)
=
k
d
]
的逆序对对数
−
∑
k
>
1
k
d
≤
n
d
p
[
k
d
]
[gcd(a_i,a_j)=d] 的逆序对对数 \\= [gcd(a_i,a_j)=kd] 的逆序对对数 - \sum_{k>1}^{kd\le n}dp[kd]
[gcd(ai,aj)=d]的逆序对对数=[gcd(ai,aj)=kd]的逆序对对数−k>1∑kd≤ndp[kd]
就是答案了。含义就是把等于
k
d
kd
kd 里的
2
d
,
3
d
,
4
d
,
…
2d,3d,4d,\dots
2d,3d,4d,… 都减掉,剩下的不就是
d
d
d 的么。
时间复杂度计算:我们从 n n n 到 1 1 1 枚举 d d d,去计算 g c d ( a i , a j ) = k d gcd(a_i,a_j)=kd gcd(ai,aj)=kd 的逆序对 ( i , j ) (i,j) (i,j) 对的对数。对一个 d d d,数组中一共有 n / d n/d n/d 个元素,这 n / d n/d n/d 个元素使用树状数组计算逆序对的时间复杂度是 O ( ⌊ n d ⌋ ∗ l o g ( ⌊ n d ⌋ ) ) O(\left\lfloor\dfrac n d \right\rfloor*log(\left\lfloor\dfrac n d \right\rfloor)) O(⌊dn⌋∗log(⌊dn⌋)) ,算所有 d d d 的 g c d ( a i , a j ) = k d gcd(a_i,a_j)=kd gcd(ai,aj)=kd 的逆序对对数的运算次数是 ∑ d = 1 n ⌊ n d ⌋ ∗ l o g ( ⌊ n d ⌋ ) ≤ ∑ d = 1 n ⌊ n d ⌋ ∗ l o g n ≈ n ∗ l o g n ∗ ∑ d = 1 n 1 d = n ∗ l o g n ∗ l o g n \sum_{d=1}^n\left\lfloor\dfrac n d \right\rfloor*log(\left\lfloor\dfrac n d \right\rfloor)\le \sum_{d=1}^n\left\lfloor\dfrac n d \right\rfloor*log\,n\approx n*log\,n\ * \sum_{d=1}^n\dfrac 1d=n*log\,n\ *log\,n\ d=1∑n⌊dn⌋∗log(⌊dn⌋)≤d=1∑n⌊dn⌋∗logn≈n∗logn ∗d=1∑nd1=n∗logn ∗logn
∑ k > 1 k d ≤ n d p [ k d ] \sum_{k>1}^{kd\le n}dp[kd] ∑k>1kd≤ndp[kd] 的枚举次数是 ⌊ n d ⌋ − 1 \left\lfloor\dfrac n d \right\rfloor-1 ⌊dn⌋−1 ,总的枚举次数是 ∑ d = 1 n ( ⌊ n d ⌋ − 1 ) ≈ n ∗ ∑ d = 1 n 1 d − n = n ∗ l o g n − n \sum_{d=1}^n(\left\lfloor\dfrac n d \right\rfloor-1)\approx n*\sum_{d=1}^n \dfrac 1 d -n=n*log\,n\ -n ∑d=1n(⌊dn⌋−1)≈n∗∑d=1nd1−n=n∗logn −n。综上,总的时间复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
code:
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;
int n,a[maxn],pos[maxn];
struct tree_arry{
int n,tr[maxn];
int lowbit(int x){return x&-x;};
void add(int x,int y){
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=y;
}
int qry(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=tr[i];
return ans;
}
int qry(int x,int y){
return qry(y)-qry(x-1);
}
void print(){
for(int i=1;i<=n;i++)
printf("%d ",tr[i]);
puts("");
}
ll f(int d){//求gcd(ai,aj)=kd的逆序对
vector<pair<int,int> > t;
for(int k=d;k<=n;k+=d)
t.push_back(make_pair(pos[k],k));
sort(t.begin(),t.end());
ll cnt=0;
for(auto x:t){
add(x.second,1);
cnt+=qry(x.second+1,n);
}
for(auto x:t){
add(x.second,-1);
}
return cnt;
}
}ta;
ll dp[maxn];
int main(){
cin>>n;
ta.n=n;
for(int i=1;i<=n;i++){
cin>>a[i];
pos[a[i]]=i;
}
for(int i=n;i>=1;i--){
dp[i]=ta.f(i);
for(int k=2*i;k<=n;k+=i)
dp[i]-=dp[k];
}
cout<<dp[1]<<endl;
return 0;
}