CodeTON Round 7 (Div. 1 + Div. 2) 解题报告
CodeTON Round 7 (Div. 1 + Div. 2)
广告:本场比赛博主使用了 CCH 完成,体验很好,推荐高 rating 用户使用(低 rating 受 cloudflare 影响很大)。
A. Jagged Swaps
\(\text{Status: \color{green}+\color{black} 00:03}\)
结论:输出 YES
当且仅当 \(a_1=1\)。
证明:
如果 \(a_1\ne1\),而 \(a_1\) 不可能参与交换,所以 \(1\) 一定无法换到 \(a_1\)。
如果 \(a_1=1\),那么如果序列未排好序,一定存在一个位置可以交换,减少一个逆序对,从而可以排好序。
时间复杂度:\(\Theta(n)\)。
const int N=15;
int n,a[N];
void Solve(int CASE)
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
puts(a[1]==1?"YES":"NO");
}
B. AB Flipping
\(\text{Status: \color{green}+\color{black} 00:09}\)
操作其实就是交换一对 \(\texttt{A}\) 和 \(\texttt{B}\)。
开头的一堆 \(\texttt{B}\) 和结尾的一堆 \(\texttt{A}\) 都是换不了的,我们把这些没用的都删掉。下面设 \(s_1=\texttt{A},s_n=\texttt{B}\)。
下面证明此时一定可以交换 \(n-1\) 次。
证:考虑何时在位置 \(n-1\) 进行操作。
如果 \(s_{n-1}=\texttt{A}\),那么直接交换 \(s_{n-1}\) 和 \(s_n\),再把交换后的 \(s_n\) 扔掉,就变成了一个长度为 \(n-1\) 的,满足上述条件的字符串。
如果 \(s_{n-1}=\texttt{B}\),那么 \(s_{1\ldots n-1}\) 满足上述条件,那么先对 \(s_{1\ldots n-1}\) 进行操作。可以证明此时有 \(s_{n-1}=\texttt{A}\),再对 \(n-1\) 进行一次操作即可。
边界情况:\(n=2\),此时只有 \(s=\texttt{AB}\),此时交换即可。
由数学归纳法原理,命题得证。
时间复杂度:\(\Theta(n)\)。
int n;
string s;
void Solve(int CASE)
{
cin>>n>>s;
int l=0,r=n-1;
for(;l<n&&s[l]=='B';l++);
for(;r&&s[r]=='A';r--);
cout<<max(r-l,0)<<endl;
}
C. Matching Arrays
\(\text{Status: \color{green}+ \color{black}00:26}\)
我们想要让 \(a\) 中其中 \(x\) 个元素尽量比 \(b\) 中的大,而其余元素尽量比 \(b\) 中的小。
那我们可以用 \(a\) 中 \(x\) 个最大的去按顺序干 \(b\) 中 \(x\) 个最小的,反之亦然。
然后随便 check 一下即可。
时间复杂度:\(\Theta(n\log n)\),瓶颈在于排序。当然你想用桶排也没人拦你。
就这 b 玩意我想 + 打了将近 20 分钟。
const int N=200005;
int n,x,b[N],bb[N];
pair<PII,int> a[N];
void Solve(int CASE)
{
cin>>n>>x;
for(int i=1;i<=n;i++)cin>>a[i].fi.fi,a[i].fi.se=i;
for(int i=1;i<=n;i++)cin>>b[i];
sort(a+1,a+n+1);
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
bb[(i-x+n-1)%n+1]=b[i];
memcpy(b+1,bb+1,n<<2);
int cnt=0;
for(int i=1;i<=n;i++)cnt+=a[i].fi.fi>b[i];
if(cnt!=x)put_ret("NO");
cout<<"YES\n";
for(int i=1;i<=n;i++)a[i].se=b[i];
sort(a+1,a+n+1,[](auto x,auto y){return x.fi.se<y.fi.se;});
for(int i=1;i<=n;i++)cout<<a[i].se<<sp_el(i,n);
}
D. Ones and Twos
\(\text{Status: \color{green}+1 \color{black}00:47}\)
设 \(f_{l,r}=a_{l}+a_{l+1}+\ldots+a_r\)。
先看查询操作。
考虑先搞出来是否存在一段前缀的和等于 \(s\)。
如果存在答案就是 \(\texttt{YES}\) 了。那什么时候答案是 \(\texttt{NO}\) 呢?
下面考虑无解的情况。
首先,如果整个数组的元素加起来还不到 \(s\),那直接就无解了。
否则,设 \(p\) 满足 \(f_{1,p}<s,f_{1,p+1}>s\)。
由于 \(a_i\) 只能取 \(1\) 或 \(2\),所以只能 \(f_{1,p}=s-1,f_{1,p+1}=s+1\)。这说明 \(a_{p+1}\) 一定为 \(2\)。
然后观察到 \(f_{2,p+1}=f_{1,p+1}-a_1=s+1-a_1\ne s\),所以 \(a_1\) 也为 \(2\)。
又由于 \(f_{2,p+2}=f_{2,p+1}+a_{p+2}=s-1+a_{p+2}\),所以 \(a_{p+2}=2\)。
以此类推。那么是不是要求 \(a\) 数组全是 \(2\) 呢?
不是的。当 \(a=[2,1,2],s=4\) 时是无解的。
可以发现,当右边顶到 \(n\) 的时候就无法滚下去了。
再稍微推一下,可以发现它只要求 \(a_1,a_2,\ldots,a_{n-p},a_{p+1},\ldots,a_n\) 都是 \(2\) 就无解。
于是做完了。\(1\) 的位置可以 set
维护,找 \(p\) 的话直接二分即可。由于带单点修改,树状数组拍上去即可。
时间复杂度:\(\Theta(n\log^2n)\),如果用倍增之类的可以降到 \(\Theta(n\log n)\)。
const int N=100005;
int n,q;
int a[N];
set<int>s1;
int c[N];
void add(int x,int v){while(x<=n)c[x]+=v,x+=x&-x;}
int query(int x){int r=0;while(x)r+=c[x],x-=x&-x;return r;}
void Solve(int CASE)
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]==1)s1.insert(i);
add(i,a[i]);
}
while(q--)
{
int op,x;
cin>>op>>x;
if(op==1)
{
int l=1,r=n;
while(l<=r)
{
int mid=l+r>>1;
if(query(mid)>x)r=mid-1;
else l=mid+1;
}//r
if(query(r)==x){puts("YES");continue;}
else if(r==n&&query(r)<x-1){puts("NO");continue;}
else puts(s1.empty()||*s1.begin()>n-r&&*s1.rbegin()<=r?
"NO":"YES");
}
else
{
int v;cin>>v;
if(a[x]==v)continue;
a[x]=v;
add(x,v==2?1:-1);
if(v==2)s1.erase(x);
else s1.insert(x);
}
}
}
E. Permutation Sorting
\(\text{Status: \color{green}+\color{black} 01:13}\)
考虑位置 \(i\),这个数肯定要转到 \(a_i\) 的位置。
正常情况下(一次走一步),那么就要走 \((a_i-i+n)\bmod n\) 步。
不妨强制把环断成长一倍的链。
然后把 \([i,a_i]\) 转成模 \(n\) 意义下的线段(注意有一些段会变成两个,此时计算是算任意一段,但是算贡献的时候两段都要算上)。
例如对于样例 \(1\):
我们可以发现,位置 \(j\) 的数字对位置 \(i\) 的数字产生 \(-1\) 的贡献,当且仅当:
存在一个 \((i,a_i)\) 产生的线段 \(X\) 与一个 \((j,a_j)\) 产生的线段 \(Y\),满足 \(Y\subseteq X\)。
例如,\([2,2]\subseteq[1,3]\),所以 \(ans_{a_1}\) 要减一。
然后树状数组从后往前维护一下贡献即可。具体我也不会讲了。
时间复杂度:\(\Theta(n\log n)\)。
const int N=1000005;
int n,a[N],ans[N];
int c[N<<1];
void add(int x,int v){while(x<=n<<1)c[x]+=v,x+=x&-x;}
int query(int x){int r=0;while(x)r+=c[x],x^=x&-x;return r;}
void Solve(int CASE)
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=n;i;i--)
if(a[i]>=i)add(a[i]+n,1);
for(int i=n;i;i--)
{
int to=a[i]<i?a[i]+n:a[i];
ans[a[i]]=(to-i)-(query(to)-query(i));
add(to,1);
}
for(int i=1;i<=n;i++)cout<<ans[i]<<sp_el(i,n);
}
F. Bracket Xoring
\(\text{Status: \color{black}NaN}\)
暂时不会。
G. Pepe Racing
\(\text{Status: \color{black}NaN}\)
暂时不会。
H. Cyclic Hamming
\(\text{Status: \color{black}NaN}\)
暂时不会。