其他分享
首页 > 其他分享> > CF 1550

CF 1550

作者:互联网

CodeForce 1550

A

考虑最少,我们可以将序列数字设为:

\[1,3,5,7,9.... \]

进行前缀和计算,找到 \(S\) 匹配到的区间值,其最少的数的个数即为对应大前缀和的下标。(这是一种时间复杂度更优的算法)

#include<bits/stdc++.h>
using namespace std;
int T,n;
int main(){
    cin>>T;
    while(T--){
        cin>>n; int a=1,ans=0;
        while(n>0) n-=a,ans++,a+=2; 
        cout<<ans<<endl;
    }
    return 0;
}

B

可分析出:\(a\) 的大小对答案无影响,\(b\) 的正负有影响。

当 \(b>0\) 我们希望其多加几个,最多可以添加 \(n\) 个 \(b\) ,答案就是 \(n*(a+b)\)

当 \(b<0\) 我们希望少加几个,尽量相同字符直接删除,此时我们计算字符不同的段数 \(cnt\)。

有两种不同序列形式:

  1. 前后数字相同,我们就先删中间不同数字的段落,然后整体删除相同数字段落,次数为 \(cnt/2+1\)
  2. 前后数字不同,我们就先删完一个数字,再整体删另一个数字,结果相同,次数为 \(cnt/2+1\)

因此便有以下代码:

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,a,b,T;
char s[N];
int main(){
    cin>>T;
    while(T--){
        scanf("%d%d%d",&n,&a,&b); scanf("%s",s+1);
        if(b>=0) printf("%d\n",n*(a+b));
        else{
            int cnt=0;
            for(int i=1;i<=n;i++) if(s[i]!=s[i-1]) cnt++;
            printf("%d\n",n*a+b*(cnt/2+1));//所以cnt/2的向下取整便是较小的连续为1或连续为0串
        }
    }
    return 0;
}

C

推导式子:对于题目中的限制条件,我们可以转化成下面两种情况:

设符合条件 \(p>q>r\) ,则:

  1. \(a_p>a_q>a_r\)
  2. \(a_p<a_q<a_r\)

也就是单增和单减这两种情况是坏的。

序列需要连续,因此我们又可以分类讨论:

  1. \(len=1\) 有 \(n\) 个好序列
  2. \(len=2\) 有 \(n-1\) 个好序列
  3. \(len=3\) 我们需要不满足单增或单减,因此进行判断即可。
  4. \(len=4\) 枚举4种不同情况的排列方式,进行同上的判断即可
  5. 长度大于4,因为序列的数字都不一样,选取三个数字,必有单增或单减情况,因此不可能成立。

所以我们便有以下代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int T,n,a[N];
bool check(int x,int y,int z){
    if((x<=y&&y<=z)||(x>=y&&y>=z)) return 0;
    else return 1;
}
int main(){
    cin>>T;
    while(T--){
        scanf("%d",&n);  int ans=2*n-1;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n-2;i++)  if(check(a[i],a[i+1],a[i+2])) ans++;
        for(int i=1;i<=n-3;i++){
            if(check(a[i],a[i+1],a[i+2])&&check(a[i],a[i+1],a[i+3])&&check(a[i],a[i+2],a[i+3])&&check(a[i+1],a[i+2],a[i+3]))
            ans++;
        }
        printf("%d\n",ans);
    }
    // system("pause");
    return 0;
}

D:

考虑数学知识+图像结合

如果直接做会发现题目条件有点多,考虑对题目化简

注意到 \(a_i+a_j=i+j\) 这个式子有很强的对称性,一般可以移项以后构造出一些等式

此题可以令 \(a_i=i+k_i\) 那么 \(a_i+a_j=i+j\) 即可转化为 \(k_i+k_j=0\) 即\(k_i=k_j\),就会有一对满足条件的数对

那么相当于对每个 \(a_i\) 赋值一个 \(k_i\) ,对于相同的数对我们希望它尽量多。画到坐标系里就是画两条分别代表 \(a_i=i+k_i\) 和 \(a_i=i−k_i\)

这里取相反数就是对应前面的"条件",每个 \(k_i\) 都对应了一种答案的取法

注意题目给的 \(l,r\) 范围很有意思,相当于在坐标系上画两个平行x轴的线来限定k ,限定等价于端点处的两个不等式 \(1−k≥l,n+k≤r\) 即有 \(k≥min(1−l,r−n)\)

图上可以显示得看出来,在这个条件下任意 \(a_i\) 都可以赋 \(+k_i\) 或者 \(−k_i\) (因为两个都满足了) 因此肯定是贪心的让数量均匀(基本不等式)

即贡献 \(lim=min(1−l,r−n)\) ,偶数时 \(lim \times \tbinom{n}{half}\),奇数时\(2lim \times \tbinom{n}{half}\)

再考虑 \(k≤lim\) 的情况,这个时候画图,会发现两边的条件被限定了,即左边的部分由于斜线下移,交点右移,因此左边的不满足 \(−k_i\) 时的情况,只能选\(+k_i\) ,右边的同理

这个时候贡献就由k的偏移情况来决定,左交点 \(L=max(1,l+k)\),右交点 \(R=min(n,r−k)\)

此时确定了\(−k_i L−1\)个,\(+k_i n−R\)个,这个时候中间还要选 \(2x+n−L+R=R−L+1\) 即 \(x=1−L+half\) 个

于是贡献\(\tbinom{R-L+1}{1-L+half}\)偶数,\(2\tbinom{R-L+1}{1-L+half}\) 奇数

当然,这个时候只需要暴力枚举 \(k\) 就好了

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int mod=1e9+7,N=2e5+5;
int T,fac[N],inv[N],n,l,r;
int qmi(int a,int b){
    int res=1; while(b){if(b&1) res=res*a%mod; b>>=1; a=a*a%mod;} return res;
}
void init(){
    fac[0]=1;for(int i=1;i<N;i++) fac[i]=(fac[i-1]*i)%mod;
    inv[N-1]=qmi(fac[N-1],mod-2);
    for(int i=N-2;~i;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int n,int m){
    if(m<0||n<m) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
    init(); cin>>T;
    while(T--){
        scanf("%lld%lld%lld",&n,&l,&r);
        int ans=0,lim=min(1-l,r-n);//枚举 k 的限制条件
        ans=lim*C(n,n/2)%mod;
        if(n&1) ans=(ans+lim*C(n,n/2)%mod);
        for(int i=lim+1;;i++){
            int L=max(1ll,l+i),R=min(n,r-i),rest=R-L+1;
            if(rest<0) break;
            ans=(ans+C(rest,n/2-(L-1)))%mod;
            if(n&1) ans=(ans+C(rest,n/2+1-(L-1)))%mod;
        }
        printf("%lld\n",ans);
    }
    // system("pause");
    return 0;
}

标签:1550,int,lim,half,CF,while,ans,mod
来源: https://www.cnblogs.com/guanlexiangfan/p/15177400.html