CodeTON Round 7 Div. 1 + 2 (CF1896)

终于把 agc 交了。

A. Jagged Swaps

模拟即可。

B. AB Flipping

显然全是 \(\texttt{B}\) 的前缀和全是 \(\texttt{A}\) 的后缀都动不了。
答案是最后一个 \(\texttt{B}\) 的位置减第一个 \(\texttt{A}\) 的位置。
WA 了一发,真服了 /oh。

C. Matching Arrays

先把 \(a,b\) 分别升序排序,然后把 \(b\) 的最后 \(x\) 个位置移到开头。
如果有解这样一定合法。

D. Ones and Twos

原题:P6859 蝴蝶与花[POI2011] LIZ-Lollipop (不带修)。

突破口肯定是序列只有 \(1\)\(2\),在这上面找找性质。

先把明显无解的询问排除掉(比如大于 \([1,n]\) 的区间和),考虑什么情况下会无解:存在一个区间 \([l,r]\) 使得 \(sum(l,r)=x-1\),且 \(a_{r+1}=2\)。这样区间和就会把 \(x\) 恰好跳过去。
进一步地,可以证明:如果 \(x-1\) 无解,那么 \(x\) 一定有解;\(x\) 无解当且仅当 \(x-1\) 的所有解后面紧跟着的数都是 \(2\)(或为序列结尾)。

考虑先二分找到一个合法的区间 \(sum(1,l)=x-1\),将它向右移动,要保证每移动一次区间的开头结尾都依然是 \(2\)。这个限制本质上是 \([1,n-l]\cup[l+1,n]\) 里面没有 \(1\)

树状数组维护一下即可。单 \(\log\) 做法就是把二分的过程换成线段树二分,不过两 \(\log\) 甚至能跑过上面两个 \(n=2\times 10^6\) 的原题,所以摆了。

下面代码的时间复杂度是 \(\mathcal{O}(n\log^2n)\)

Code
const int N=2e5+5;
int T,n,q,a[N];
struct BIT
{
    int tr[N];
    il void clear() {for(int i=1;i<=n;i++) tr[i]=0;}
    il void add(int x,int k) {for(;x<=n;x+=x&(-x)) tr[x]+=k;}
    il int query(int x) {int res=0;for(;x;x-=x&(-x)) res+=tr[x];return res;}
    il int ask(int l,int r) {return query(r)-query(l-1);}
}tr;
int main()
{
    T=read();
    while(T--)
    {
        n=read(),q=read(); tr.clear();
        for(int i=1;i<=n;i++) a[i]=read(),tr.add(i,a[i]);
        while(q--)
        {
            int op=read(),x=read();
            if(op==2)
            {
                int v=read();
                int now=tr.ask(x,x);
                tr.add(x,v-now);
                continue;
            }
            if(x==1)
            {
                if(tr.ask(1,n)==2*n) printf("NO\n");
                else printf("YES\n");
                continue;
            }
            int l=0,r=n;
            while(l<r)
            {
                int mid=(l+r+1)>>1;
                if(tr.ask(1,mid)<=x-1) l=mid;
                else r=mid-1;
            }
            if(l!=n&&tr.ask(1,l+1)==x) {printf("YES\n");continue;}
            if(tr.ask(1,l)!=x-1) {printf("NO\n");continue;} 
            if(tr.ask(1,n-l)!=2*(n-l)||tr.ask(l+1,n)!=2*(n-l)) printf("YES\n");
            else printf("NO\n");
        }
    }
    return 0;
}

E. Permutation Sorting

\(pos_i\) 表示数 \(i\)\(a\) 序列里的位置。

\(pos_i\)\(i\) 连边,一次操作就相当于让所有边的起点移到最近的下一个不是自环的点。

考虑连向 \(i\) 的边的起点 \(u\) 移到 \(j\) 的时候 \(j\) 是不是自环,其实就是比较 \(pos_i\)\(pos_j\) 谁离 \(j\) 更近。设 \(dis(x,y)\) 表示不考虑自环,\(x\) 移动到 \(y\) 的距离。
那么进一步考虑总贡献,\(pos_i\) 移到 \(i\) 的过程中跳过的自环数就是 \(dis(pos_j,j)<dis(pos_i,j)\)\(j\) 的个数。

左边那一半对于每个 \(j\) 来说是定值,右边根据 \(j\)\(pos_i\) 的位置关系分类讨论一下是个公差为 \(1\) 的等差数列。

那么问题就是对于给定的序列 \(c_i=dis(pos_i,i)\),令 \(b_{l,\dots,r}=x,x+1,\dots,x+r-l+1\),求 \(\sum\limits_{i=l}^r [c_i < b_i]\)。把定义改为 \(c_i=dis(pos_i,i)-i\),二维数点即可。

Code
const int N=2e6+5;
int T,n,a[N],pos[N],dis[N],ans[N];
struct node{int k,tp,id;};
vector<node> q[N];
il void add(int l,int r,int x,int id) {q[l-1].push_back({x-(l),-1,id}),q[r].push_back({x-(l),1,id});}
struct BIT
{
    int tr[N];
    il void clear() {for(int i=1;i<=n+n+1;i++) tr[i]=0;}
    il void add(int x,int k) {for(;x<=n+n+1;x+=x&(-x)) tr[x]+=k;}
    il int query(int x) {int res=0;for(;x;x-=x&(-x)) res+=tr[x];return res;}
    il int ask(int l,int r) {return query(r)-query(l-1);}
}tr;
void solve()
{
    for(int i=1;i<=n;i++)
    {
        tr.add(dis[i]-(i)+n+1,1);
        for(auto x:q[i]) ans[x.id]+=x.tp*tr.ask(x.k+n+1,n+n+1);
    }
    for(int i=0;i<=n;i++) q[i].clear();
}
int main()
{
    T=read();
    while(T--)
    {
        n=read(); tr.clear();
        for(int i=1;i<=n;i++) ans[i]=0;
        for(int i=1;i<=n;i++) a[i]=read(),pos[a[i]]=i;
        for(int i=1;i<=n;i++) dis[i]=(i>=pos[i])?i-pos[i]:(n+i-pos[i]);
        for(int i=1;i<=n;i++)
        {
            int x=pos[i],ans=0;
            if(pos[i]<=i) add(pos[i],i-1,0,i);
            else add(pos[i],n,0,i),add(1,i-1,n-pos[i]+1,i);
        }
        solve();
        for(int i=1;i<=n;i++) printf("%d ",ans[i]);
        printf("\n");
    }
    return 0;
}

F. Bracket Xoring

Description

给定一个长度为 \(2n\)\(\texttt{01}\)\(s\)。一次操作定义为:

  • 选择一个长度为 \(2n\) 的合法括号串(所有括号均匹配);
  • 对于每个左括号,设它的位置为 \(l\),与它匹配的右括号位置为 \(r\),将 \(s\)\([l,r]\) 这个区间取反(\(\texttt{0}\) 变成 \(\texttt{1}\)\(\texttt{1}\) 变成 \(\texttt{0}\))。

请构造方案在 \(10\) 次操作内将 \(s\) 的所有位置变为 \(\texttt{0}\),并输出方案。

多组数据,\(T\le 10^3\)\(\sum n\le 2\times 10^5\)

Solution

先找一些比较容易直接看出来的性质。

  • 每次操作都一定会改变 \(s_1\)\(s_{2n}\),故 \(s_1\neq s_{2n}\) 无解;
  • 每次操作都会取反偶数个位置,故 \(s\) 中有奇数个 \(1\) 无解。

注意到只要这个字符串满足 \(\forall i\bmod 2=0,s_i=s_{i+1}\)(不含首尾),就可以用一次操作让整个字符串变成一样的。

先假设这里的 \(s_1=\texttt{1}\),如果不是可以先做一次 \(\texttt{()()...()}\) 操作将整个串取反。

具体地,从开头开始令每两个相邻的 \(\texttt{1}\) 配成一对括号,显然这些括号位置的取反次数都是 \(1\);每两个相邻的 \(\texttt{1}\) 之间一定有偶数个 \(\texttt{0}\),我们把它们两两配对成 \(\texttt{()()...}\) 的形式,那么它们因为包含在一组 \(\texttt{1}\) 形成的括号内,均不会被取反。
例如 \(s=\texttt{1000011001}\),则构造操作 \(\texttt{(()())(())}\)

所以我们如果能构造一些前置操作使字符串满足这个条件就做完了。

考虑对于一个任意的串,怎么让 \(s_i\)\(s_{i+1}\) 相等:

  • 若本身就 \(s_i=s_{i+1}\),在这里放一对 \(\texttt{()}\),那么这两位的取反状态一定是一样的;
  • 否则要么放两个左括号要么放两个右括号,我们根据前面未匹配的括号数判断:如果有未匹配的括号(显然这不会是一个奇数),在这里放 \(\texttt{))}\),否则放 \(\texttt{((}\)

第二种情况一定出现偶数次(否则不满足有偶数个 \(1\)),所以这样构造出的括号串一定是匹配的。

综上,我们至多使用三步操作使 \(s\) 全为 \(0\)

写代码的时候精神状态堪忧,可能和上面讲的没什么关系。(upd: 重写了。)

Code
const int N=4e5+5;
int T,n,a[N],b[N],c[N];
char s[N];
int main()
{
    T=read();
    while(T--)
    {
        n=read()<<1; scanf("%s",s+1);
        for(int i=1;i<=n;i++) a[i]=s[i]-'0';
        int cnt=0; for(int i=1;i<=n;i++) cnt+=a[i];
        if(a[1]!=a[n]||(cnt&1)) {printf("-1\n");continue;}
        if(a[1])
        {
            printf("3\n"); 
            for(int i=1;i<=n;i++) a[i]^=1,printf(i&1?"(":")"); printf("\n");
        }
        else printf("2\n");
        int sum=0; a[1]^=1,a[n]^=1;
        printf("(");
        for(int i=2;i<n;i+=2) 
        {
            if(a[i]==a[i+1]) printf("()");
            else if(!sum) printf("(("),a[i+1]^=1,sum^=1;
            else printf("))"),a[i]^=1,sum^=1;
        }
        printf(")\n(");
        for(int i=2;i<n;i+=2) printf(a[i]?")(":"()");
        printf(")\n");
    }
    return 0;
}

G. Pepe Racing

Description

这是一道交互题。

\(n^2\) 个人,每个人有一个跑步速度,保证任意两人的速度互不相同。
每次你可以询问互不相同\(n\) 个人,交互库会告诉你他们中跑得最快的人是谁。
请在 \(2n^2-2n+1\) 次询问内求出速度前 \(n^2-n+1\) 快的人的排名顺序,并输出答案。

多组数据,\(T\le 10^4,\ n\le 20,\ \sum n^3 \le 3\times 10^5\)

Solution

考虑一个一个找。

\(n+1\) 次操作找出最快的人是容易的:先分别找 \([1,n],[n+1,2n],\dots,[n(n-1)+1,n^2]\) 中最快的人,再把这 \(n\) 个人拼在一起询问一次。

不过每个都这么找显然很蠢。
找第二快的人时,只有最快的人所在的那一组需要把他排除掉重新询问,剩下的答案都不用变。需要排除掉一个人的那组就用不是现在任何一组最大值的东西来补空位。这样除了第一个人以外每个都只需要询问 \(2\) 次,总次数是 \(n+1+2(n^2-n)=2n^2-n+1\)

还要压掉 \(n\) 组询问,怎么办呢?然后就不会做了。

考虑只剩下 \(2n-1\) 个人没确定排名的时候的局面:
\(n\) 个不同的数分别作为 \(n\) 组的答案,剩下的 \(n-1\) 个数由于要用来补空位,它们在每组都不重不漏地出现一次。这条性质给局面提供了一些信息。

\(n-1\) 个数在每组都出现过,却都不是答案,那么一定是跑得最慢的 \(n-1\) 个人。我们不关心他们的相对排名。

所以只需要找出其余 \(n\) 个人的相对顺序就可以了。那么直接询问这 \(n\) 个人,他们中最快的那个每次都能只用 \(1\) 次询问找出。因为最后一个可以直接排除,这个步骤一共只会进行 \(n-1\) 次。

这样询问次数就变成了 \(n+2(n^2-2n+1)+n-1=2n^2 -2n+1\),正好可以通过。

Code
const int N=405;
int n,ans[N],vis[N];
set<int> s[N];
vector<int> Ans;
il int ask(set<int> &s)
{
    cout<<"? ";for(auto x:s) cout<<x<<" ";cout<<endl;
    return read();
}
il void get(set<int> &s)
{
    int tot=1;
    for(int i=n-s.size();i;i--) 
    {
        while(vis[tot]||s.find(tot)!=s.end()) tot++;
        s.insert(tot);
    }
}
il void solve()
{
    n=read();
    for(int i=0;i<=n*n+1;i++) vis[i]=0,ans[i]=0,s[i].clear();
    Ans.clear();
    for(int i=1;i<=n;i++)
    {
        for(int j=(i-1)*n+1;j<=i*n;j++) s[i].insert(j);
        ans[i]=ask(s[i]),vis[ans[i]]=1;
    }
    for(int tot=n*n;tot>=2*n;tot--)
    {
        set<int> now;
        for(int i=1;i<=n;i++) now.insert(ans[i]);
        get(now); int x=ask(now),id=0;
		for(int i=1;i<=n;i++) if(ans[i]==x) id=i;
        Ans.push_back(x);
        s[id].erase(x),get(s[id]);
        ans[id]=ask(s[id]),vis[ans[id]]=1;
        for(int i=1;i<=n;i++) if(i!=id) s[i].erase(ans[id]);
    }
    set<int> now;
    for(int i=1;i<=n;i++) now.insert(ans[i]);
    get(now);
    for(int tot=n-1;tot;tot--)
    {
        int x=ask(now); now.erase(x),get(now);
        Ans.push_back(x);
    }
    for(auto x:now) if(vis[x]) Ans.push_back(x);
    cout<<"! ";for(auto x:Ans) cout<<x<<" "; cout<<endl;
}
int main() {int T=read();while(T--) solve();return 0;}

H. Cyclic Hamming

不会啊,摆了。

posted @ 2023-11-28 16:56  樱雪喵  阅读(141)  评论(3编辑  收藏  举报