Codeforces Round #740 (Div. 1) 部分题题解

B.Up the Strip

题目链接

Up the Strip

简要题解

我们可以自然地设出状态\(F[i]\),表示有多少种方案能移动到\(i\)
假设我们现在已经得到了\(F[i]\)的值,考虑从\(i\)处继续往下走。

对于第一种操作,实际上是将\(F[1]\)~\(F[i-1]\)全部加上\(F[i]\),我们可以用一个维护差分的树状数组来实现。
对于第二种操作,对于每一个\(i\),大约可以转移给\(\sqrt{i}\)个数,显然是不能暴力转移的。
我们倒过来想,对于一个\(i\),有哪些数可以通过第二种操作转移到它,那么我们枚举\(j\),满足\(\lfloor\frac{x}{j}\rfloor=i\)\(x\)一定是一段连续区间。
我们枚举\(j\),算出对应区间的贡献和,这也可以用树状数组实现。
不难发现,枚举\(j\)的复杂度是\(O(n*logn)\)的,因此总复杂度为\(O(n*log^2n)\)
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e6+10;
int n,Mod,F1[MAXN],F2[MAXN];
int Read()
{   int a=0,c=1;   char b=getchar();
    while(b!='-'&&(b<'0'||b>'9')) b=getchar();
    if(b=='-') c=-1,b=getchar();
    while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
    return a*c;
}
int Min(int A,int B){   return A<B?A:B;   }
int Lowbit(int K){   return K&(-K);   }
int Add(int A,int B){   return A+=B,A>=Mod?A-Mod:A;   }
void Modify(int *F,int Np,int Num){   while(Np<=n) F[Np]=Add(F[Np],Num),Np+=Lowbit(Np);   }
int Query(int *F,int Np)
{   int Ret=0;
    while(Np>=1) Ret=Add(Ret,F[Np]),Np-=Lowbit(Np);
    return Ret;
}
int main()
{   scanf("%d%d",&n,&Mod),F1[n]=1;
    for(int i=n,Nv=0;i>=1;i--)
    {   for(int j=2,Le,Ri;i*j<=n;j++)
            Le=i*j,Ri=Min(n,Le+j-1),Nv=Add(Nv,Add(Query(F2,Ri),Mod-Query(F2,Le-1)));
        Nv=Add(Nv,Query(F1,i)),Modify(F1,1,Nv),Modify(F1,i,Mod-Nv),Modify(F2,i,Nv),Nv=0;
    }
    printf("%d\n",F2[1]);
}

C.Bottom-Tier Reversals

题目链接

Bottom-Tier Reversals

简要题解

我们需要把整个序列排序,由于每次只能翻转前缀,因此一定是把元素从后往前依次归位。
由于只能翻转奇数长度的前缀,所以奇数位上的数不能移到偶数位上,反之亦然。
那么若奇数位上存在偶数,或者偶数位上存在奇数,就一定无法还原序列。

接下来讨论从后往前还原序列,假设当前需要还原第\(i\)位和第\(i-1\)位,其中\(i\)是奇数。
我们设当前第\(i\)个位置上的数为\(b_i\),那么有以下几种情况:

\(Case 1:i,i-1,...,b_i,i+1,i+2,...,n\)
    我们只需要将前\(i\)个位置翻转即可还原\(i\)\(i-1\),总共\(1\)步还原。
\(Case 2:b_1,b_2,...,i-1,i,...,b_i,i+1,i+2,...,n\)
    我们只需要将以\(i\)为结尾的前缀翻转即可变成\(Case1\)的情况,总共\(2\)步还原。
\(Case 3:b_1,b_2,...,i,i-1,...,b_i,i+1,i+2,...,n\)
    我们只需要将任意一个包含\(i\)\(i-1\)的前缀翻转即可变成\(Case2\)的情况,总共\(3\)步还原。
\(Case 4:i,b_2,...,i-1,...,b_i,i+1,i+2,...,n\)
    我们只需要将\(i-1\)前面的位置翻转即可变成\(Case3\)的情况,总共\(4\)步还原。
\(Case 5:b_1,b_2,...,i-1,...,i,...,b_i,i+1,i+2,...,n\)
    我们只需要将以\(i\)为结尾的前缀翻转即可变成\(Case4\)的情况,总共\(5\)步还原。
\(Case 6:b_1,b_2,...,i,...,i-1,...,b_i,i+1,i+2,...,n\)
    我们只需要将以\(i\)为结尾的前缀翻转即可变成\(Case4\)的情况,总共\(5\)步还原。

可以发现,我们至多花五步就可以还原两个位置,因此在\(\frac{5}{2}n\)步内一定可以还原整个序列。
可以直接暴力翻转,时间复杂度\(O(n^2)\)
代码如下:

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int MAXN=2050;
int T,n,Flag,A[MAXN];
vector<int>Ans;
void Solve(int Lp,int Rp,int Aim)
{   if(Rp==Aim&&Lp==Aim-1) return ;
    if(Rp==1&&Lp==2) return reverse(A+1,A+Aim+1),Ans.pb(Aim);
    if(Rp==Lp+1) return reverse(A+1,A+Rp+1),Ans.pb(Rp),Solve(2,1,Aim);
    if(Lp==Rp+1)
    {   int Nt=Lp+1;
        for(int i=Nt;i<=Aim;i+=2)
            if(A[i+1]==A[1]-1) Nt=i;
        return reverse(A+1,A+Nt+1),Ans.pb(Nt),Solve(Nt-Lp+1,Nt-Rp+1,Aim);
    }
    if(Rp==1) return reverse(A+1,A+Lp),Ans.pb(Lp-1),Solve(Lp,Lp-1,Aim);
    if(Rp>Lp) reverse(A+1,A+Rp+1),Ans.pb(Rp),Solve(Rp-Lp+1,1,Aim);
    if(Rp<Lp) reverse(A+1,A+Rp+1),Ans.pb(Rp),Solve(Lp,1,Aim);
}
int main()
{   for(scanf("%d",&T);T;T--)
    {   scanf("%d",&n),Flag=0,Ans.clear();
        for(int i=1;i<=n;i++) scanf("%d",&A[i]),Flag|=(A[i]^i)&1;
        if(Flag){   puts("-1");   continue ;   }
        for(int i=n,Lp,Rp;i>=2&&!Flag;i-=2)
        {   for(int j=1;j<=i;j++) A[j]==i-1?Lp=j:0,A[j]==i?Rp=j:0;
            Solve(Lp,Rp,i);
        }
        if(Flag||Ans.size()>5*n/2){   puts("-1");   continue ;   }
        printf("%d\n",Ans.size());
        for(int v:Ans) printf("%d ",v);
        if(Ans.size()) puts("");
    }
}

D.Top-Notch Insertions

题目链接

Top-Notch Insertions

简要题解

我们设排序完成后的数组为\(\{b_i\}\),由题意得\(b_i\leq b_{i+1}\),然后考虑我们现在有什么限制条件。
如果对于序列中的某个位置\(p\),不存在\(x_i=p\)\((x_i,y_i)\),那么\(a_p\)就是前\(p\)个元素中第\(p\)大的,此时允许元素相等的情况。
如果对于序列中的某个位置\(p\),存在\(x_i=p\)\((x_i,y_i)\),那么\(a_p\)就是前\(p\)个元素中第\(y_i\)大的,并且严格小于第\(y_i+1\)大的元素。
我们发现,对序列中每一个位置都分析一次之后,我们就能把\(\{a_i\}\)\(\{b_i\}\)一一对应起来,所以我们只要求有多少种\(\{b_i\}\)

对于每一个\(b_i\),有两种限制。
一是题目原本的限制,需要满足\(b_i\leq b_{i+1}\)
二是来自\((x,y)\)的限制,每一个\((x,y)\)会形成一个\(b_y<b_{y+1}\)的限制。我们不关心\(j\)具体是多少,但是我们要知道有几个这样的限制。
不同的\((x_i,y_i)\)对可能会形成相同的\(b_y<b_{y+1}\)的限制,因此我们需要想办法去重。

不妨按\(x\)从小到大处理每一对\((x,y)\),假设当前处理\((x_i,y_i)\),它会贡献一个\(b_{y_i}<b_{y_i+1}\)的限制。
我们发现,加入第\(x_i\)个元素之后,先前已有的限制会发生变化。
对于所有\(k\geq y_i-1\)的限制\(b_k<b_{k+1}\)来说,它们会变成\(b_{k+1}<b_{k+2}\),这个可以自己手玩推导一下。
那么变化之后,如果已经存在\(b_{y_i}<b_{y_i+1}\)的限制,那么我们的限制就重复了一次。
具体实现可以用平衡树,维护哪些位置有第二种限制,需要支持插入元素,区间加法,查询某个元素是否存在。

去重之后,假设我们知道\(m\)\((x,y)\)产生了\(K\)个限制,然后考虑如何确定\(\{b_i\}\)
先说结论,满足条件的\(\{b_i\}\)\(C_{2*n-K-1}^{n}\)个。
为什么?我们考虑构造数列\(\{c_i\}\),初始令\(c_i=b_i\)
然后对于每一个限制\(b_j\leq b_{j+1}\),我们把\(c_{j+1}\)~\(c_n\)全部加一。
这样构造出来的\(\{c_i\}\)\(\{b_i\}\)是一一对应的,而且\(c_i\)各不相同。
不难发现,\(1\leq c_i\leq 2*n-K-1\),所以我们只需要从值域中挑\(n\)个不同的值就能确定一个\(\{b_i\}\)
时间复杂度\(O(m*logn)\)
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
const int Mod=998244353;
int T,n,m,Ans,K,Fac[MAXN*2],Inv[MAXN*2];
int Read()
{   int a=0,c=1;   char b=getchar();
    while(b!='-'&&(b<'0'||b>'9')) b=getchar();
    if(b=='-') c=-1,b=getchar();
    while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
    return a*c;
}
int Pow(int Down,int Up)
{   int Ret=1,Now=Down;
    for(;Up>=1;Up>>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod;
    return Ret;
}
int C(int A,int B){   return B>A||B<0?0:1ll*Fac[A]*Inv[B]%Mod*Inv[A-B]%Mod;   }
namespace Treap
{   int Root,H1,H2,H3,Ts,Fix[MAXN],Num[MAXN],Son[MAXN][2],Size[MAXN],Lan[MAXN];
    int New(int S){   return Ts++,Fix[Ts]=rand(),Num[Ts]=S,Size[Ts]=1,Ts;   }
    void Push_up(int S){   Size[S]=Size[Son[S][0]]+Size[Son[S][1]]+1;   }
    void Push_down(int S)
    {   if(!Lan[S]) return ;
        if(Son[S][0]) Num[Son[S][0]]+=Lan[S],Lan[Son[S][0]]+=Lan[S];
        if(Son[S][1]) Num[Son[S][1]]+=Lan[S],Lan[Son[S][1]]+=Lan[S];
        Lan[S]=0;
    }
    int Merge(int A,int B)
    {   if(!A||!B) return A+B;
        Push_down(A),Push_down(B);
        if(Fix[A]>Fix[B]) return Son[A][1]=Merge(Son[A][1],B),Push_up(A),A;
        return Son[B][0]=Merge(A,Son[B][0]),Push_up(B),B;
    }
    void Split(int S,int Lim,int &R1,int &R2)
    {   if(!S) return R1=R2=0,(void)0;
        Push_down(S);
        if(Num[S]<=Lim) R1=S,Split(Son[S][1],Lim,Son[S][1],R2),Push_up(S);
        else R2=S,Split(Son[S][0],Lim,R1,Son[S][0]),Push_up(S);
    }
    void Insert(int Val){   Split(Root,Val-1,H1,H2),Root=Merge(Merge(H1,New(Val)),H2);   }
    int Query(int Val,int Ret=0){   return Split(Root,Val,H1,H2),Ret=Size[H1],Root=Merge(H1,H2),Ret;   }
    void Modify(int Val)
    {   Split(Root,Val-1,H1,H2);
        if(H2) Num[H2]++,Lan[H2]++;
        Root=Merge(H1,H2);
    }
    void Init()
    {   for(int i=0;i<=Ts;i++) Fix[i]=Num[i]=Son[i][0]=Son[i][1]=Size[i]=Lan[i]=0;
        Root=Ts=0;
    }
}using namespace Treap;
int main()
{   Fac[0]=Inv[0]=1,srand(time(NULL));
    for(int i=1;i<=400000;i++) Fac[i]=1ll*Fac[i-1]*i%Mod;
    Inv[400000]=Pow(Fac[400000],Mod-2);
    for(int i=399999;i>=1;i--) Inv[i]=1ll*Inv[i+1]*(i+1)%Mod;
    for(T=Read();T;T--)
    {   n=Read(),m=Read(),Ans=0,K=m,Init();
        for(int i=1,Xi,Yi;i<=m;i++)
        {   Xi=Read(),Yi=Read(),Modify(Yi-1);
            if(Query(Yi)!=Query(Yi-1)) K--;
            else Insert(Yi);
        }
        printf("%d\n",C(2*n-1-K,n));
    }
}
posted @ 2021-08-25 23:37  Alkaid~  阅读(104)  评论(0编辑  收藏  举报