CF2063

CF2063A

题意

区间 \([l,r]\) 是一个好区间当且仅当 \(l,r\) 互质且它的真子区间都不是好区间。每次询问 \([l,r]\) 内有多少个好区间。

解法

发现一个事实是对于任意 \(x\ge2\) 都有 \([x,x]\) 不是好区间,\([x,x+1]\) 是好区间。这是因为 \(x,x\) 有公共的大于 \(1\) 的质数 \(x\),而对于每个 \(x\) 大于 \(1\) 的因数 \(d\),都有 \(x+1\equiv1\pmod d\),因此 \(x,x+1\) 没有公共的大于 \(1\) 的质数,且它的真子区间 \([x,x],[x+1,x+1]\) 都不是好区间。

另外 \([1,1]\) 是好区间。但是当 \(r>1\) 时,可以把 \([1,1]\) 视作 \([1,2]\),答案就为 \(r-l\)。否则 \(l=r=1\),答案为 \(1\)

时间复杂度 \(O(1)\)

代码

#include<cstdio>
int t,l,r;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&l,&r);
		if(r==1&&l==1)puts("1");
		else printf("%d\n",r-l);
	}
} 

CF2063B

题意

给定序列,选择它的一个子序列翻转,问一个区间的最小可能值。

解法

考虑最后求和是一个区间,从这个角度出发。

那么我们执行翻转操作本质就是要去区间外找一些小的数替换进来,因此只需要思考如下三种情况:

  1. 将任意 \(k\) 个在 \([l,r]\) 左侧的数替换任意 \(k\) 个区间中的数,其中 \(k\le r-l+1\)。这是容易实现的,只需要选择区间左侧的任意想翻转的 \(k\) 个数,和区间内部的 \(k\) 个数,一翻就翻进来替换掉了。

  2. 将任意 \(k\) 个在 \([l,r]\) 右侧的数替换任意 \(k\) 个区间中的数,与上面没有本质区别。

  3. 既翻一些左边的进来,又翻一些右边的进来,这是不可能的。因为选取的子序列中的元素,翻转前后的距离是不变的。比如选取子序列 \(17,19,25,1725,1925\),翻转后变成 \(1925,1725,25,19,17\),其中 \(17,25\) 的距离总是 \(2\)。由于我们要翻转后有原来左边的元素 \(ll\),又有原来右边的元素 \(rr\),设在区间 \([l,r]\) 选取的元素个数为 \(z\),翻转前 \(ll,rr\) 距离大于 \(z\),翻转后距离右小于等于 \(z\),矛盾。

于是答案就为 \([1,r]\) 中最小的 \(r-l+1\) 个数的和与 \([l,n]\) 中最小的 \(r-l+1\) 个数的和的较小值。

使用快速排序求答案时间复杂度 \(O(n\log n)\),当然也很容易让它变成 \(O(n)\)

代码

#include<cstdio>
#include<algorithm>
int t,n,l,r,a1[100005],a2[100005],a3[100005],n1,n2,n3;
long long ans1,ans2;
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&l,&r);ans1=ans2=0;
        n1=l-1,n2=r-l+1,n3=n-r;
        for(int i=1;i<=n1+n2;i++)scanf("%d",&a2[i]);
        for(int i=n1+1;i<=n1+n2;i++)a3[n3+i-n1]=a2[i];
		std::sort(a2+1,a2+n2+n1+1);
        for(int i=1;i<=n3;i++)scanf("%d",&a3[i]);std::sort(a3+1,a3+n3+n2+1);
        for(int i=1;i<=n2;i++)ans1+=a2[i];
        for(int i=1;i<=n2;i++)ans2+=a3[i];
        printf("%lld\n",std::min(ans1,ans2));
    }
}

CF2063C

题意

给定一棵树,删两个点最多能让它变成几个连通块。

解法

因为原图无环,每删一个点原图就会多出 \(k-1\) 个连通块,其中 \(k\) 是它的边数。删一个点多出的连通块个数就是 \(\max k-1\),及度数最高的点相连的边数。

由于删一个点最多会对别的点连的边数造成至多 \(1\) 的影响。因此要枚举一个点,然后根据它是否与所有度数最高的点相连来判断应该加上 \(\max k-1\) 还是 \(\max k-2\)

时间复杂度 \(O(n)\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
int n;
std::vector<std::vector<int> >v(200005);
int max,mmx,ans,a[200005];
void Main(){
    scanf("%d",&n);max=mmx=ans=0;
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        v[a].push_back(b),v[b].push_back(a);
    }
    for(int i=1;i<=n;i++)max=std::max(max,(int)v[i].size());
    for(int i=1;i<=n;i++)if((int)v[i].size()==max){
        mmx++;
        for(int vv:v[i])a[vv]++;
    }//printf("%d %d\n",max,mmx);
    for(int i=1;i<=n;i++)if(mmx>1||(int)v[i].size()!=max){
        if(a[i]+((int)v[i].size()==max)!=mmx)ans=std::max(ans,(int)v[i].size()+max-1);
        else ans=std::max(ans,(int)v[i].size()+max-2);
    }
    for(int i=1;i<=n;i++)v[i].clear(),a[i]=0;
    printf("%d\n",ans);
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

CF2063D

题意

两个有序整数数组 \(a,b\),长度不一样(一个是 \(n\),另一个是 \(m\)),但都为 \(O(n)\),每次要么以获得 \(a_r-a_l\) 的收益取走 \(a_l,a_r,b_i\),要么以获得 \(b_r-b_l\) 的收益取走 \(b_l,b_r,a_i\)。问最多取多少次,假设这个值是 \(k\),那么需要回答对于 \(i=1,2\cdots k\),恰好取 \(i\) 次的最大收益。

解法

对于 \(a\),显然应该依次从两头选,否则不会更优。

那依次计算 \(a_1+a_n,a_2+a_{n-1},\cdots\),就得到了新的数组 \(c\)。同理对 \(b\) 操作,就得到了数组 \(d\)。注意,后文无特殊说明数组、元素都不指这两个数组,因为它们只是辅助数组。

我们可以不先计算 \(k\),由于 \(k\le {n\over3}\),只需要思考第 \(i\) 和第 \(i+1\) 步的关系。

设第 \(i\) 步选择了 \(l_0\)\(a\)\(l_1\)\(b\),由于这是最优的,所以我们在此基础上再选一个更优的就行了,即比较 \(c_{l_0+1},d_{l_1+1}\)。但是注意到在选的时候还需要浪费一个另一个数组的元素,很有可能某个数组选不了了但是可以通过把选它时最劣的一步删掉,改选另一个数组,这样就节约了一个元素。于是总结选取策略如下:

  1. 当剩余可选元素个数不足 \(3\) 个时,或者某一个数组没有选但另一个数组选的次数达到了它的长度,退出选取过程。

  2. 当某个数组剩余可选个数不足 \(1\) 个时,撤销之前最劣的一步,改选另一个数组,保证这一步还可选,并执行步骤 4,3。

  3. 选择 \(c_{l_0+1},d_{l_1+1}\) 的较大值。

  4. 根据操作更新 \(l_0,l_1\)

但是如何算 \(k\) 呢?推出选取过程前取的个数不就是 \(k\) 吗。但是貌似可以用一个式子算出来。

貌似是预先排好序的,所以是 \(O(n)\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
int n,m;
int a[200005],b[200005],aa[200005],bb[200005];
long long ans;
std::vector<long long>an;
void Main(){
    an.clear();
    scanf("%d%d",&n,&m);ans=0;
    if(n<m){
        std::swap(n,m);
        for(int i=1;i<=m;i++)scanf("%d",&b[i]);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    }
    else{
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=m;i++)scanf("%d",&b[i]);
    }
    std::sort(a+1,a+n+1);
    std::sort(b+1,b+m+1);
    for(int i=1;i<=n/2;i++)aa[i]=a[n-i+1]-a[i];
    for(int i=1;i<=m/2;i++)bb[i]=b[m-i+1]-b[i];
    int la=0,lb=0;
    for(int i=1;i<=n;i++){//printf("%d %d:",la,lb);
        if(n+m-(3*(la+lb))<3)break;
        if(la==0&&lb==n)break;
        if(la==m&&lb==0)break;
        if(2*lb+la==m){
            ans=ans+aa[la+1]-bb[lb],la++,lb--;
        }
        if(2*la+lb==n){
            ans=ans+bb[lb+1]-aa[la],la--,lb++;
        }
        if((2*la+lb<n-1)&&(2*lb+la==m-1||aa[la+1]>bb[lb+1]))ans+=aa[la+1],la++;
        else ans+=bb[lb+1],lb++;
        an.push_back(ans);
    }
    printf("%d\n",an.size());
    for(long long i:an)printf("%lld ",i);
    puts("");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

CF2063E

题意

给一棵 \(n\) 个点的有根树,求:

\[\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}f(dis(i,lca(i,j)),dis(j,lca(i,j))) \]

其中 \(lca(x,y)\) 表示两个节点的最近公共祖先,\(dis(x,y)\) 表示两点在树上简单路径经过的边数。\(f(x,y)\) 表示整数 \(z\) 的个数使 \(x,y,z\) 构成非退化三角形。

解法

换根 DP。

那根据经验,先算出 \(size_i,d_i,dd_i\) 前两个是子树大小、深度。后面一个是深度不小于 \(i\) 的节点数量。这些后面当然是有用的。

观察 \(f(x,y)\)\(z\) 应当在区间 \((|x-y|,x+y)\) 内,整数个数也就是 \(x+y-|x-y|-2\) 个。于是有如下性质:

  1. \(x<y\) 时是 \(2x-2\)\(x\) 增加 \(1\) 值增加 \(2\)

  2. \(x\ge y\) 时,是 \(2y-2\)\(x\) 无关。

  3. \(f(x,1)=1\)

下面就从这些性质推出树上的转移式:

设节点 \(u\) 的答案(\(\sum_{j=1}^{n}f(dis(u,lca(u,j)),dis(j,lca(u,j)))\))为 \(ans_u\),考虑从 \(u\) 转移到它的一个儿子 \(v\)\(ans_v\) 来自两部分:第一部分是整棵树除了 \(u\) 的子树,这一部分已经被 \(ans_u\) 计算了,由于 \(f(x,y)\) 只有 \(x<y\) 时与 \(x\) 相关,因此转移影响到的点只有深度大于等于 \(v\) 的深度的点(当然不包含 \(v\) 的子树),到每个 LCA 的距离都多了 \(1\),因此这部分点每个点的值都增加 \(2\);第二部分是 \(u\) 子树除了 \(v\) 子树的点,它们与 \(v\) 的 LCA 是 \(u\),根据 \(f(x,1)=1\),加上这些点的个数即可。于是转移式便是:

\[ans_v=ans_u+2(dd_{d_v}-size_v)+size_u-size_v \]

注意根据 \(ans_u\) 的定义,每对点会算两次,因此答案是:

\[{1\over2}\sum_{i=1}^n ans_i \]

另外边界条件 \(ans_1=0\)

时间复杂度 \(O(n)\)

代码

#include<cstdio>
#include<vector>
int n,dd[300005],d[300005],size[300005];
long long ans,an[300005];
std::vector<std::vector<int> >v(300005);
void dfs1(int now=1,int d=1,int p=0){
    ::d[now]=d;dd[d]++;
    size[now]=1;
    for(int vv:v[now])if(vv-p)dfs1(vv,d+1,now),size[now]+=size[vv];
}
void dfs(int now=1,int p=0){
    if(now-1)an[now]=an[p]+size[p]-size[now]-1+2*(dd[d[now]]-size[p]+1);
    ans+=an[now];
    for(int vv:v[now])if(vv-p)dfs(vv,now);
}
void Main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)dd[i]=0;ans=0;dd[n+1]=0;
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        v[a].push_back(b),v[b].push_back(a);
    }
    dfs1();
    for(int i=n;i;i--)dd[i]+=dd[i+1];
    dfs();
    for(int i=1;i<=n;i++)v[i].clear();
    printf("%lld\n",ans/2);
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

CF2063F1 and CF2063F2

题意

有一个长度为 \(2n\) 的括号序列,依次告诉你 \(n\) 对匹配的括号,需要在每次告诉你之前和之后回答随便填没告诉你的位置,能构成多少个合法的括号序列。

解法

F1

首先 \(n\) 对括号组成的合法的括号序列是 Catalan 数,这里记作 \(H_n\)

可以假设外面套了一个括号把整个串包起来。

对于每个括号,如果它中间未被里面的括号串包含(直接包含)的空位置个数为 \(k\),则这 \(k\) 个位置需要成为一个合法的括号串,有 \(H_k\) 种。而答案应当是 \(H_k\) 的乘积。

比如对于括号序列 ((..)...(....(....)..).) 最大的括号直接包含 \(4\) 个空位置,其他几个括号直接包含 \(2,6,4\) 个空位置,因此总共有 \(H_4H_2H_6H_4\) 种方案。

证明也很简单,一对匹配的括号中的括号需要满足左括号数等于右括号数,且任意前缀左括号数不应当小于右括号数,这与括号串没有本质区别。

Catalan 数能够预处理。新加入的括号只会改变最小的一个包含它的括号对答案的贡献。只需要每次加括号时改变最小的一个包含它的括号的 \(k\) 值,以及通过枚举所有它包含的位置来计算这个新括号对的 \(k\) 值。能够 \(O(n^2)\) 解决。这样,可以通过 F1。

F2

注意到时间复杂度的瓶颈是枚举一个括号包含的位置有没有其他括号。

用线段树可以找最小的包含一个括号的括号对。对于每个 \(l,r\) 括号对,在 \(l\) 处插入 \(r\),用 MAX 线段树,每次查询的就是 \([0,l]\) 中最后一个大于 \(r\) 的位置。

同样可以用线段树找区间中已经被更小的括号对包含的位置。对于每个位置,最小的一个包含它的括号对右边的一个是 \(r\),则在 \(r\) 处加 \(1\)。这样查询 \([l,r]\) 的和即可,因为给定的括号对是合法的。如果查询的值是 \(s\)。那么在线段树 \(r\) 位置加上 \(r-l-1-s\) 即完成了对线段树的维护。

时间复杂度 \(O(n\log n)\)

代码


#include<cstdio>
#include<utility>
#include<algorithm>
int n,sgt[4000005],tag[4000005],ans,l,r;
int ak[4000005],mod=998244353,inv[4000005],q[4000005],qv[4000005];
int stt[4000005];
void aa(int x,int v,int l=0,int r=2*n+1,int o=1){
    stt[o]+=v;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(x<=mid)aa(x,v,l,mid,o*2);
    else aa(x,v,mid+1,r,o*2+1);
}
int qq(int ll,int rr,int l=0,int r=2*n+1,int o=1){
    if(ll<=l&&rr>=r)return stt[o];
    int mid=(l+r)>>1;
    if(ll<=mid&&rr>mid)return qq(ll,rr,l,mid,o*2)+qq(ll,rr,mid+1,r,o*2+1);
    if(ll<=mid)return qq(ll,rr,l,mid,o*2);
    return qq(ll,rr,mid+1,r,o*2+1);
}
void build(int l=0,int r=2*n+1,int o=1){
    sgt[o]=tag[o]=0;stt[o]=0;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(l,mid,o*2);
    build(mid+1,r,o*2+1);
}
void add(int ll,int rr,int l=0,int r=2*n+1,int o=1){
    if(rr>sgt[o])sgt[o]=rr,tag[o]=ll;
    if(l==r)return void();
    int mid=(l+r)>>1;
    if(ll<=mid)add(ll,rr,l,mid,o*2);
    else add(ll,rr,mid+1,r,o*2+1);
}
std::pair<int,int>qu(int ll,int rr,int vv,int l=0,int r=2*n+1,int o=1){
	if(sgt[o]<vv)return {-1e9,-1e9};
    if(l==r)return {tag[o],sgt[o]};
    int mid=(l+r)>>1;
    if(ll<=l&&rr>=r){
        if(sgt[o*2+1]>vv)return qu(ll,rr,vv,mid+1,r,o*2+1);
        return qu(ll,rr,vv,l,mid,o*2);
    }
    if(ll<=mid&&rr>mid)return std::max(qu(ll,rr,vv,l,mid,o*2),qu(ll,rr,vv,mid+1,r,o*2+1));
    if(ll<=mid)return qu(ll,rr,vv,l,mid,o*2);
    return qu(ll,rr,vv,mid+1,r,o*2+1);
}
int lto[2000005];
void Main(){
    scanf("%d",&n);ans=q[n],ak[0]=2*n;build();add(0,2*n+1);
    
    printf("%d ",ans);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&l,&r);
        std::pair<int,int> x=qu(0,l,r);
		int su=qq(l,r);add(l,r);lto[l]=i;int xx=lto[x.first];
        ak[i]=r-l-1-su;ans=1ll*ans*(q[ak[i]/2])%mod;aa(r,ak[i]+2);aa(x.second,-ak[i]-2);
        ans=1ll*ans*qv[ak[xx]/2]%mod;ak[xx]-=ak[i]+2;ans=1ll*ans*q[ak[xx]/2]%mod;
        printf("%d ",ans);
    }
    puts("");
}
int mul(int a,int x){
    if(!x)return 1;
    int t=mul(a,x/2);
    if(x%2)return 1ll*t*t%mod*a%mod;
    return 1ll*t*t%mod;
}
int main(){
    inv[0]=q[0]=qv[0]=inv[1]=1;
    for(int i=1;i<=5e5;i++)inv[i+1]=mul(i+1,mod-2),q[i]=1ll*q[i-1]*(2*i)%mod*(2*i-1)%mod*inv[i]%mod*inv[i+1]%mod,qv[i]=mul(q[i],mod-2);
    int t;
    scanf("%d",&t);
    while(t--)Main();
}

posted @ 2025-01-24 09:26  AAhaoxuan  阅读(69)  评论(0)    收藏  举报