10.8离线赛

来源:互联网 发布:电力人工智能与机器人 编辑:程序博客网 时间:2024/06/05 08:08

一、弹钢琴

数据: 对于70%,N∈[1,1000]
对于100%,N∈[1,1e9],K∈[1,50]

排列组合的一道裸题。两种实现排列组合的方法:

1、变为递推式计算。因为对于一个组合(aCb)=((a-1) C (b))+((a-1) C (b-1))。然后按照双重循环过去就好了。

#include<bits/stdc++.h>#define Mod 1000000007using namespace std;int A[100005],dp[100005][55];int main(){    int n,k;    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++)scanf("%d",&A[i]);    sort(A+1,A+1+n);    for(int i=0;i<=n;i++)dp[i][0]=1;    for(int i=1;i<=n;i++)        for(int j=1;j<=k;j++)            dp[i][j]=(dp[i-1][j]+dp[i-1][j-1])%Mod;    //处理出索要用到的所有C。用dp实现    long long ans=0;    for(int i=k;i<=n;i++)        ans=(ans+(1LL)*dp[i-1][k-1]*A[i])%Mod;    //直接加,但是要边加边模    printf("%lld\n",ans);    return 0;}

2、用乘法逆元。乘法逆元大概就是a/b%p,其中p是质数,就可以把式子写成a*(b^(p-2)) mod p。然后就可以一边循环过去吧C也算出来。对于b^(p-2),用快速幂就行了

#include<bits/stdc++.h>#define Mod 1000000007#define ll long long#define M 100005using namespace std;ll A[M],C[M];ll f(ll x){//快速幂    ll res=1;    int n=Mod-2;    while(n){        if(n&1)res=res*x%Mod;        x=x*x%Mod;        n>>=1;    }    return res;}int main(){    int n,k;    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++)scanf("%lld",&A[i]);    sort(A+1,A+1+n);    C[k-1]=1;    for(int i=k;i<=n;i++)C[i]=C[i-1]%Mod*i%Mod*f((1LL)*(i-k+1))%Mod;//乘法逆元    ll ans=0;    for(int i=k;i<=n;i++)ans=(ans+C[i-1]*A[i])%Mod;    printf("%lld\n",ans);    return 0;}

二、删除数字

数据:对于30%,A∈[1,100],N∈[1,10]
对于70%,A∈[1,1e5]
对于100%,A∈[2,1e12],N∈[1,100]

这种题一看就是dp。但是考试时前面时间浪费太多了,只留了五分钟打了个记忆化搜索,还运行错误了。
删除当做1,不删当做0,那么一个长度为12的数的状态只有2^12=4096种,这样就够了。每次把上一个数在某种状态下数字比自己小的dp值转过来就行。

#include<bits/stdc++.h>#define ll long long#define Mod 1000000007using namespace std;ll A[105],cnt[4105];struct node{    ll a,sum;    bool operator < (const node &x)const{        return a<x.a;    }}dp[105][4105];ll f(int x,int y){//把A[x]中删除掉几个数    bool Q[15]={0};    int num[15]={0};    int a=1,i=1;    while(i<=y){//哪几个数位要删标为1        if(y&i)Q[a]=1;        a++;i<<=1;    }    ll c=A[x];int k=0;    while(c)num[++k]=c%10,c/=10;//分解    ll b=0;    for(int i=k;i>=1;i--)//合并        if(Q[i])b=b*10+num[i];    return b;}//这样写有点麻烦,可以再简单点int main(){    ll a;int n,len=0;    scanf("%lld%d",&a,&n);    for(int i=0;i<=n;i++)A[i]=a+i;    while(a)len++,a/=10;    for(int i=1;i<(1<<len);i++){//0位置要先处理出来        dp[0][i].sum=1;        dp[0][i].a=f(0,i);    }    for(int i=1;i<=n;i++){        sort(dp[i-1]+1,dp[i-1]+(1<<len));//写法比较神奇        for(int j=1;j<(1<<len);j++)            cnt[j]=(cnt[j-1]+dp[i-1][j].sum)%Mod;        //前缀和方便计算        for(int j=1;j<(1<<len);j++){            ll x=f(i,j);            int y=upper_bound(dp[i-1],dp[i-1]+(1<<len),(node){x,0})-dp[i-1]-1;//找一个符合的数            dp[i][j].a=x;            dp[i][j].sum=cnt[y];//然后把数都加过来        }    }    ll ans=0;    for(int i=0;i<(1<<len);i++)        ans=(ans+dp[n][i].sum)%Mod;    printf("%lld\n",ans);    return 0;}

除了用二分查找,也可以用归并。下降方案数弄出来,然后直接排序,用归并找就行了,具体看admin

三、四点旅行
数据:对于60%,N∈[10,200]
对于100%,N∈[10,2000]

这道题可以分成两个问题:
1、快速求出任意两点之间的最短路径
2、快速求出四个点的路径值

对于1,不能看到是图就只用图的算法。因为是有向图并且边权为1,完全可以用BFS,这样就只有N*M。而要是用其他的,像Dijkstra就算加上优先级队列优化也要N^2 * log M

对于2,四个点中ad两个点是独立的,只与bc两个点有关系,那就之枚举bc,就只有N^2。注意要事先与处理出来和b相连的最长边和c的最长边。每个都要三条,因为要避免abcd四个点重复出现

#include<bits/stdc++.h>#define FOR(i,a,b) for(int i=(a);i<=(b);i++)using namespace std;vector<int>A[2][2005];int dis[2][2005][2005],n,m;bool mark[2005];struct node{int id,v;};queue<node>Q;void f(int p,int st){//BFS求最短路,p是因为有向图,b->a要反着弄,其实可以不用,但这样清晰一点    memset(mark,0,sizeof mark);    mark[st]=1;    Q.push((node){st,0});    while(!Q.empty()){        node x=Q.front();Q.pop();        dis[p][st][x.id]=x.v;        FOR(i,0,A[p][x.id].size()-1)            if(!mark[A[p][x.id][i]]){                Q.push((node){A[p][x.id][i],x.v+1});                mark[A[p][x.id][i]]=1;            }    }}struct node1{int id[4];}G[2][2005];int main(){    scanf("%d%d",&n,&m);    FOR(i,1,m){        int x,y;        scanf("%d%d",&x,&y);        A[0][x].push_back(y);        A[1][y].push_back(x);    }    FOR(i,1,n)f(0,i),f(1,i);//最短路    FOR(p,0,1)FOR(i,1,n)FOR(j,1,n)//预处理每个点下的最长的三条路径        if(dis[p][i][j]!=0){            FOR(k,1,3)                if(dis[p][i][j]>dis[p][i][G[p][i].id[k]]){                    for(int l=3;l>k;l--)//只用三条,较短的往后移                        G[p][i].id[l]=G[p][i].id[l-1];                    G[p][i].id[k]=j;                    break;                }        }    int ans=0;    FOR(b,1,n)FOR(c,1,n)//枚举bc两个点        if(dis[0][b][c]!=0){//这两个点存在路径            FOR(i,1,3)if(G[1][b].id[i]!=b&&G[1][b].id[i]!=c){//找一个a点,a不与c重合                FOR(j,1,3)                    if(G[0][c].id[j]!=b&&G[0][c].id[j]!=G[1][b].id[i]){找一个d点,d不与ab重合                        ans=max(ans,dis[1][b][G[1][b].id[i]]+dis[0][c][G[0][c].id[j]]+dis[0][b][c]);//更新                    }            }        }    printf("%d\n",ans);    return 0;}

这一次时间把握的不好,第一道题写了这么久不仅没对,还没留下时间写第二题;还有排列组合把握的不扎实,要再研究一下。

原创粉丝点击