Educational Codeforces Round 36 (Rated for Div. 2) 題解
Educational Codeforces Round 36 (Rated for Div. 2)
題目的質量很不錯(不看題解做不出來,笑
Codeforces 920C
題意
給定一個\(1\)到\(n\)組成的數組,只可以交換某些相鄰的位置,問是否可以將數組調整為升序的
解題思路
首先如果每個數都能通過交換到它應該到的位置,那麽就可以調整為升序的。
但實際上交換是對稱的,如果應該在的位置在當前位置前方的數都交換完成,那麽整體就是排好序的,因為不可能所有不在相應位置的數都在相應位置的後方。
所以我們從\(1\)到\(n\)掃一遍,在掃描的過程中維護當前位置能向前交換的位置的最小值,這樣就可以判斷了
AC代碼
#include <bits/stdc++.h>
using namespace std;
int n,pos[300020],num[300020];
string str;
bool judge()
{
int maxpre=1;
for (int i=1;i<=n;i++)
{
if(i>=2)
if(str[i-2]==‘0‘) maxpre=i;
if(pos[i]>=i) continue;
if(maxpre>pos[i]) return false ;
}
return true;
}
int main(int argc, char const *argv[])
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&num[i]);
pos[num[i]]=i;
}
cin>>str;
if(judge())
puts("YES");
else
puts("NO");
return 0;
}
Codeforces 920E
題意
給定一個無向圖,求補圖的連通分量數和每個連通分量的大小
點數\(n \le 200000\) ,邊數\(m \le 200000\)
(原題?:bzoj1098)
解題思路
雖然直接對補圖用\(BFS\)求連通分量數復雜度是\(O(n)\)的,但光建圖復雜度就要\(O(n^2)\)了,所以直接建補圖肯定是不行的
這裏用到一個神奇的方法,在對原圖\(BFS\)最開始維護一個所有未分配在連通分量中的點的集合,當前點不能到達的點就是補圖能到達的點,將補圖能到達的點加入隊列繼續\(BFS\),對於已經判斷在某個連通分量的點,需要在集合中刪去。
這個集合可以直接使用std::set<int>
,也可以使用鏈表,在這裏我使用鏈表
這樣只需要建原圖就可以對補圖進行\(BFS\)了,復雜度\(O(n+m)\)
AC代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
vector <int> G[maxn];
int scc[maxn],scccnt;
int pre[maxn],nxt[maxn],vis[maxn],deled[maxn];
int n,m;
void del(int u)
{
nxt[pre[u]]=nxt[u];
pre[nxt[u]]=pre[u];
}
void bfs(int s)
{
queue<int> Q;
Q.push(s);
del(s);
while(!Q.empty())
{
int u=Q.front();Q.pop();
if(vis[u]) continue;
vis[u]=true;
scc[scccnt]++;
for (int v:G[u])
deled[v]=true;
for (int i=nxt[0];i<=n;i=nxt[i])
if(!deled[i])
{
del(i);Q.push(i);
}
for (int v:G[u])
deled[v]=false;
}
scccnt++;
}
int main(int argc, char const *argv[])
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n+1;i++) pre[i]=i-1;
for (int i=0;i<=n;i++) nxt[i]=i+1;
for (int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
for (int i=nxt[0];i<=n;i=nxt[i])
if(!vis[i])
bfs(i);
sort(scc,scc+scccnt);
printf("%d\n",scccnt);
for (int i=0;i<scccnt;i++)
printf("%d ",scc[i]);
puts("");
return 0;
}
Codeforces 920F
題意
定義\(D(x)= Card\{k:k| x,k \in N^+\}\)
給定數組\(a\),做如下操作:
replace l r:將\(a_l\)到\(a_r\)替換為\(D(a_i)\)
sum l r:輸出\(a_l\)到\(a_r\)的和
點數\(n \le 300000\),操作數\(m \le 300000\) ,\(a_i \le 1000000\)
解題思路
\(D(x)\)可以直接用篩法求出,復雜度\(O(MAXN \log MAXN)\)
首先顯然要用線段樹進行維護,但是直接套線段樹對區間的每個點都單點更新顯然是過不了的
註意到對\(D(x)\)叠代收斂很快,根據官方題解,在數據範圍內叠代不超過\(6\)次就可以收斂到\(2\)或\(1\)
我們可以用\(2\)棵線段樹,一棵維護和,一棵維護最大值,當區間更新時對整個區間進行遞歸地更新,如果當前子樹的最大值$ \le 2$, 那麽可以直接返回,這樣對於每個點最多不更新超過\(6\)次
復雜度\(O(m \log n+MAN \log MAXN)\)
AC代碼
#include <bits/stdc++.h>
using namespace std;
const int maxnum=1e6+6;
const int maxn=3e5+7;
int D[maxnum],a[maxn];
int n,m;
long long sumv[(maxn<<2)+5];
int maxv[(maxn<<2)+5];
void build(int now,int l,int r)
{
if(l==r)
{
maxv[now]=sumv[now]=a[l];
return;
}
int mid=l+((r-l)>>1);
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
sumv[now]=sumv[now<<1]+sumv[now<<1|1];
maxv[now]=max(maxv[now<<1],maxv[now<<1|1]);
}
void update(int now,int l,int r,int ul,int ur)
{
if(maxv[now]<=2) return; //關鍵
if(l==r)
{
maxv[now]=sumv[now]=D[sumv[now]];
return;
}
int mid=l+((r-l)>>1);
if(ul<=mid) update(now<<1,l,mid,ul,ur);
if(ur>mid) update(now<<1|1,mid+1,r,ul,ur);
sumv[now]=sumv[now<<1]+sumv[now<<1|1];
maxv[now]=max(maxv[now<<1],maxv[now<<1|1]);
}
long long query_sum(int now,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr) return sumv[now];
int mid=l+((r-l)>>1);
long long ans=0;
if(ql<=mid) ans+=query_sum(now<<1,l,mid,ql,qr);
if(qr>mid) ans+=query_sum(now<<1|1,mid+1,r,ql,qr);
return ans;
}
void pre_solve()
{
for (int i=1;i<=1e6;i++)
{
for (int j=i;j<=1e6;j+=i)
D[j]++;
}
}
int main(int argc, char const *argv[])
{
pre_solve();
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
while(m--)
{
int t,l,r;scanf("%d%d%d",&t,&l,&r);
if(t==1)
update(1,1,n,l,r);
else
printf("%I64d\n",query_sum(1,1,n,l,r));
}
return 0;
}
Educational Codeforces Round 36 (Rated for Div. 2) 題解