10.8離線賽

一、彈鋼琴

數據: 對於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 1000000007
using 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 100005
using 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 1000000007
using 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;
}

這一次時間把握的不好,第一道題寫了這麼久不僅沒對,還沒留下時間寫第二題;還有排列組合把握的不紮實,要再研究一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章