CF961试题总结(不定期更新完)

题目传送门
A,B,C是zz题,直接贴代码
//A
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream> 
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
} 
int n,m,f[1005],ans;
int main()
{
    n=read();m=read();ans=2e9;
    for(int i=1;i<=m;i++){
        int x=read();f[x]++;
    }
    for(int i=1;i<=n;i++) ans=min(ans,f[i]);
    printf("%d",ans);
    return 0;
}
//B
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
} 
ll n,k,f[100005],vis[100005],ans,sum[100005],res;
int main()
{
    n=read();k=read();
    for(ll i=1;i<=n;i++) f[i]=read();
    for(ll i=1;i<=n;i++){
        vis[i]=read();if(vis[i]) ans+=f[i];
    }
    for(ll i=1;i<=k;i++) if(!vis[i]) sum[1]+=f[i];res=sum[1];
    for(ll i=2;i<=n-k+1;i++){
        sum[i]=sum[i-1];
        if(!vis[i-1]) sum[i]-=f[i-1];
        if(!vis[i+k-1]) sum[i]+=f[i+k-1];
        res=max(res,sum[i]);
    }
    printf("%d",res+ans); 
    return 0;
}
//C
#include<cstdio>
#include<string>
#include<iostream> 
#define ll long long
using namespace std;
int n,res=2e9;
char se[5][106][105],k;
char f[205][205];
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int gos(int a,int b,int c,int d){
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){f[i][j]=se[a][i][j];}
    for(int i=1;i<=n;i++)for(int j=n+1;j<=n*2;j++){f[i][j]=se[b][i][j-n];}
    for(int i=n+1;i<=n*2;i++)for(int j=1;j<=n;j++){f[i][j]=se[c][i-n][j];}
    for(int i=n+1;i<=n*2;i++)for(int j=n+1;j<=n*2;j++){f[i][j]=se[d][i-n][j-n];}
    int t1=0,t2=0;
    for(int i=1;i<=n*2;i++)for(int j=1;j<=n*2;j++){if(f[i][j]-'0'!=(((i-1)*n*2+j+i&1)&1))t1++;else t2++;}
    return min(t1,t2);
}
int main()
{
    scanf("%d\n",&n);
    for(int i=1;i<5;i++){
      for(int j=1;j<=n;j++)
        scanf("%s",se[i][j]+1);getchar();
    }
    for(int i=1;i<=4;i++)
     for(int j=1;j<=4;j++)
      for(int k=1;k<=4;k++)
       for(int q=1;q<=4;q++){
         if(i!=j&&j!=k&&k!=q&&i!=k&&i!=q&&j!=q){res=min(res,gos(i,j,k,q));}
       }
    printf("%d",res);
    return 0;
}

D题我考试的时候打了个random_shuffle操过去了,但是被hack了,其实这个算法还是挺强的,也贴一下。
random_shuffle的思路还是比较简单,我们每次取五个数,如果这五个数不能被两条直线覆盖,则答案直接输出NO,而当我们进行成千上万遍的random_shuffle后,还不输出NO,那么就有极大地概率是YES,我们直接输出YES即可。防超时打个clock。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<ctime>
using namespace std;
int read(){
    char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
    x=x*10+c-'0';return x*y;    
}
int n,vis[10];
struct node{
    int x,y;
}F[100005]; 
double k(int x,int y){
    return (double)(F[x].y-F[y].y)/(F[x].x-F[y].x);
}
int check(){
    for(int i=2;i<=5;i++){
        vis[1]=vis[i]=1;
        for(int j=1;j<=5;j++){
            if(vis[j]) continue;
            if((double)k(1,j)==(double)k(1,i)) vis[j]=1;
        }
        int re=0,p1=0,p2=0;
        for(int j=1;j<=5;j++){
            if(vis[j]) continue;
            if(!p1){p1=j;continue;}
            if(!p2){p2=j;continue;}
            if((double)k(p1,p2)!=(double)k(p1,j)){re=1;break;}
        }
        if(!re) return 1;
        memset(vis,0,sizeof(vis));
    }
    return 0;
}
int main()
{
    n=read();
    if(n<=4){
        puts("YES");return 0;
    }
    for(int i=1;i<=n;i++) F[i].x=read(),F[i].y=read();
    srand(20180404);
    for(int k=1;k<=50000;k++){
        if(clock()>=1960){
            puts("YES");return 0;
        }
        random_shuffle(F+1,F+1+n);
        if(!check()){
            puts("NO");return 0;
        }
    }
    puts("YES");
    return 0;
}
D题正解的思路也挺简单的,就是我们取三个不在同一直线上的点,这三个点两两连一条线。可以用反证法证明,如果原图可以用两条线覆盖,则其中一条线必为这三条之一。所以我们枚举三条直线,删去在他们上的点,如果剩下的点在同一直线上,就输YES。若枚举过三条线依旧没有,就输NO。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<ctime>
using namespace std;
int read(){
    char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9') 
    x=x*10+c-'0';return x*y;
}
struct node{
    int x,y;
}F[100005];
double k(int x,int y){
    if(F[x].x==F[y].x&&F[x].y<F[y].y) swap(x,y);
    return (double)(F[x].y-F[y].y)/(F[x].x-F[y].x);
}
int n,vis[100005];
int check(int x,int y,int z){
    memset(vis,0,sizeof(vis));vis[1]=vis[2]=vis[3]=1;
    double base=k(x,y),now;int pl=0;
    for(int i=4;i<=n;i++) 
      if(k(x,i)==base) vis[i]=1;
    for(int i=4;i<=n;i++) if(!vis[i]){pl=i;break;}vis[pl]=1;
    if(!pl) return 1;now=k(z,pl);
    for(int i=4;i<=n;i++) 
      if(!vis[i]&&now==k(z,i)) vis[i]=1;
    for(int i=1;i<=n;i++) if(!vis[i]) return 0;
    return 1;
}
int main()
{
    n=read();srand(20180409);
    for(int i=1;i<=n;i++) F[i].x=read(),F[i].y=read();
    memset(vis,0,sizeof(vis));
    for(int i=3;i<=n;i++)
      if(k(1,2)!=k(1,i)){swap(F[3],F[i]);break;}
    if(check(1,2,3)){puts("YES");return 0;}
    if(check(1,3,2)){puts("YES");return 0;}
    if(check(2,3,1)){puts("YES");return 0;}
    puts("NO");return 0;
    return 0;
}

E题:
E题是一道数据结构题。题意大致是有N个数,第i个数为ai,若对于i,j满足:
i< j,ai>=j,aj>=i,则称i,j为错误的数对,求错误的数对的总和。
这道题可以用主席树写,X_O_R大佬的程序。
#include <cstdio>
#include <cctype>
const int maxn=200005,maxp=3800095;
int N,A[maxn];
long long Ans;
inline void read(int &Res){
    char ch=getchar(); bool fl=0;
    for (Res=0;!isdigit(ch);ch=getchar()) if (ch=='-') fl^=1;
    for (;isdigit(ch);ch=getchar()) Res=(Res<<3)+(Res<<1)+ch-48;
    if (fl) Res=(~Res)+1;
}
struct PT{
    int Root[maxn],x,y,len,sul;
    struct Ad{int l,r,cnt;}T[maxp];
    void Build(int &p,int L,int R){
        T[++len]=T[p],++T[p=len].cnt;
        if (L>=R) return; int mid=(L+R)>>1;
        if (x>mid) Build(T[p].r,mid+1,R);
            else Build(T[p].l,L,mid);
    }
    void Query(int u,int v,int L,int R){
        if (x<=L&&R<=y){sul+=T[v].cnt-T[u].cnt; return;}
        if (L>=R) return; int mid=(L+R)>>1;
        if (x<=mid) Query(T[u].l,T[v].l,L,mid);
        if (y>mid) Query(T[u].r,T[v].r,mid+1,R);
    }
    inline void Create(int id,int num){
        x=num,Build(Root[id]=Root[id-1],1,N);
    }
    inline int GetAns(int L,int R,int frm,int to){
        sul=0; if (L>R||frm>to) return sul;
        x=frm,y=to,Query(Root[L-1],Root[R],1,N);
        return sul;
    }
}T;
int main(){
    read(N);
    for (int i=1;i<=N;++i){
        read(A[i]);
        if (A[i]>N) A[i]=N;
        T.Create(i,A[i]);
    }
    for (int i=1;i<=N;++i)
        Ans+=T.GetAns(i+1,A[i],i,N);
    printf("%I64d",Ans);
    return 0;
}
然而我们可以用更为巧妙的思路来解题,我们可以按照ai从小到大sort一遍,然后我们先将所有的点都插入树状数组里。接着我们就可以枚举i了,对于每个i,我们可以在树状数组里删掉aj< i的点。然后我们又可以查询小于ai的j有多少,也就是树状数组里1到a[i]的前缀和。这样就可以求出答案。由于这样会重复,所以最后ans要除以2。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll n,top,ans,sum[400005],a[400005];
struct node{
    ll num,pl;
}F[400005];
ll cmp(node a,node b){
    return a.num<b.num;
}
ll lowbit(ll x){return x&-x;}
void add(ll x,ll ad){
    while(x<=n+1){
        sum[x]+=ad;
        x+=lowbit(x);
    }
}
ll getnum(ll x){
    ll res=0;
    while(x){
        res+=sum[x];
        x-=lowbit(x);
    }
    return res;
}
int main()
{
    n=read();top=1;
    for(ll i=1;i<=n;i++){
        a[i]=read();a[i]=min(a[i],n+1);F[i].num=a[i];F[i].pl=i;add(i,1);
    }
    sort(F+1,F+1+n,cmp);
    for(ll i=1;i<=n;i++){
        while(top<=n&&i>F[top].num) add(F[top].pl,-1),top++;
        ans+=getnum(a[i]);
        if(a[i]>=i) ans--;
    }
    printf("%lld",ans>>1);
    return 0;
}

F题,听JYZ大佬说是回文自动机,不会。等会了再说

G题
这是一道数论题。首先我们看题目n个数取成k个集合,这肯定和第二类斯特林数有关。
第二类斯特林数描述为:将n个不同的球放入m个无差别的盒子中,要求盒子非空,有几种方案?
关于第二类斯特林数,有递推公式:s(i,j)=s(i-1,j-1)+j×s(i-1,j)
同时第二类斯特林数有展开公式如下图

这里写图片描述

我们考虑每个点对答案的贡献,首先不管它怎么分,它至少会出现在s(n,k)种不同分类的集合里,所以ans先加上sum*s(n,k)。其中sum为∑ai。
我们考虑剩下的点,剩下的点能构成s(n-1,k)种集合,而我们将点i加入不同的集合里,会获得不同的收益,但应为公n-1个数,所以最后收益也就乘上n-1。所以最终的答案为:
(s(n,k)+(n-1)*s(n-1,k))*sum
这道题由于200000的数据范围,需要用到斯特林数展开公式,同时在求组合数时,乘法逆元也非常的重要。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#define md 1000000007
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll pows(ll a,ll b){
    ll base=1;
    while(b){
        if(b&1) base=base*a%md;
        a=a*a%md;b/=2;
    }
    return base;
}
ll n,m,sum,f[200005];
ll c(ll x,ll y){
    return f[x]*pows(f[y],md-2)%md*pows(f[x-y],md-2)%md;
}
ll s(ll x,ll y){
    ll res=0;
    for(ll i=0;i<=y;i++) 
      res=(res+pows(-1,i)%md*c(y,i)%md*pows(y-i,x)+md)%md;
    res=res*pows(f[y],md-2)%md;
    return res%md;
}
int main()
{
    n=read();m=read();
    f[0]=f[1]=1;for(ll i=2;i<=n;i++) f[i]=f[i-1]*i%md;
    for(ll i=1;i<=n;i++) sum=(sum+read())%md;
    printf("%I64d",(s(n,m)%md+(n-1)*s(n-1,m)%md)%md*sum%md);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/79844881