數學測試4(test20170323)

【巨胖的技能組合】(Dnf.pas/c/cpp Time:1s Memory:256M)

【問題描述】

巨胖是打DNF的高手。高手中的高手。
巨胖有N種技能,他1分鐘之內可以釋放M次技能。其中有K種技能,因爲無色啊藍啊CD啊等各種原因,每分鐘有一個使用次數上限Li。由於巨胖的技術、裝備、人品均爲一流,所以其他技能都可以無限制釋放。對於兩個巨胖一分鐘內釋放M次技能的方案,若存在一個技能使得這兩個方案中這個技能的使用次數不同,那麼這兩個方案視爲不同的方案。巨胖爲了追求潮炫酷,想知道不同的技能釋放方案有多少。

【輸入】

輸入文件名爲Dnf.in。
輸入第一行三個整數,分別爲N,K,M,其表示意義如上所述。
輸入第二行爲K個正整數,代表那K種技能一分鐘內的使用上限。

【輸出】

輸出文件名爲Dnf.out。
輸出一行一個正整數,爲不同的技能釋放方案總數對1000000007的模值。

【輸入輸出樣例】

Dnf.in
2 0 3

Dnf.out
4

【樣例解釋】
不同的方案爲4種,分別爲(0,3) (1,2) (2,1) (3,0)。

【數據範圍】

對於20%的數據,保證有N,M5
對於50%的數據,保證有N,M100
對於80%的數據,保證有N,M1000
對於100%的數據,保證有N,M105 ,KMin(N,15)

【題解】

大意是要釋放M次技能,有N種技能可以釋放,其中K種技能釋放次數有限,其他技能釋放次數無限,求釋放技能的方案數。

20分算法:

直接搜素,複雜度自己算

50分&80分算法:

其實可以考慮動歸,F[i,j]表示已經考慮了前i個技能,已經釋放了j個技能的方案數。裸轉移方程就是F[i][j]=jk=max{0,jl[i]}F[i1][k] ,l[i]表示第i個技能的釋放次數,如果沒有限制則爲無窮大。(50分),又因爲F[i][j] 每次取的都是連續一段的和,那麼這可以用前綴和維護。時間複雜度可以降一維。(80分)

100分算法:

動歸已經不好優化了,觀察題目別的性質:帶限制的技能數量很小。我們要求所有限制技能的釋放次數在限制次數範圍內。一種方法容斥原理求解:首先設N種技能的使用次數分別爲x1,x2,x3xn ,那麼由題意可以得到式子x1+x2+x3+xn=M ,然後要求前k個元的解集分別在0到某個數,其他都爲非負整數,求它的解的數量。先求出至少0個限制技能使用過度的方案數:即N種元素可重選M個的可重組合。即CN+M1N 。然後求出至少1個限制技能使用過度的方案數,假設這個過度使用的技能標號爲I,則原式變爲x1+x2+(xi+li+1)xn=Nx1+x2++xi++xn=Nli1 。仍然可以可重組合。至少2個的,至少3個的……都同理。最後只需要預處理下逆元即可。

【代碼】

80分代碼:

#include <cstdio>
#include <algorithm>
#include <cstring>

#define Rep(i,s,t) for(int i=s;i<=t;i++)
#define For(i,s,t) for(int i=s;i;i=t)

using namespace std;

typedef long long LL;

const int mod = 1000000007;
const int maxm = 20;
const int maxx = 2000;

const int Inf = (unsigned)(-1) >> 1;

int n,m,t;
int cnt[maxx];
LL f[maxx][maxx];
int ans,tmp;
bool flag;

namespace half_score{

    int main(){
        f[0][0] = 1;
        memset(cnt,0x7f/3,sizeof(cnt));
        scanf("%d%d%d",&n,&t,&m);
        Rep( i , 1 , t ) scanf("%d",&cnt[i]);
        Rep( i , 1 , n ) Rep( j , 0 , m )
            f[i][j] = (f[i][j] + f[i-1][j] - (f[i-1][(max(j-cnt[i]-1,0) == 0 && j-cnt[i]-1 != 0)? m+1 : j-cnt[i]-1]) + f[i][j==0? m+1 : j-1]) % mod;
        printf("%d",f[n][m] >= 0? f[n][m]%mod : f[n][m]%mod+mod);
    }

}

int main(){
    freopen("dnf.in","r",stdin);
    freopen("dnf.out","w",stdout);

    half_score :: main();

    return 0;
}

100分代碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int size = 200000;
const int MOD = 1000000007;

LL n,k,m,ans;
LL need[30],ches[30];
LL jie[size+10],ni[size+10];
int num[50];

void pre();
LL calc(LL x,LL y,LL p);
LL ksm(LL x,LL y,LL p);
void dfs(int x);

int main() {
    freopen("dnf.in","r",stdin);
    freopen("dnf.out","w",stdout);
    scanf("%lld%lld%lld",&n,&k,&m);
    pre();
    if(m==0) 
        return puts("1"),0;
    for(int i=1;i<=k;i++) 
        scanf("%lld",&need[i]);
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}

LL ksm(LL x,LL y,LL p) {
    if(y==0) return 1;
    if(y==1) return x%p;
    LL ret=ksm(x,y/2,p);
    ret=ret*ret%p;
    if(y&1) 
        ret=ret*(x%p)%p;
    return ret;
}

LL calc(LL x,LL y,LL p) {
    if(x<y) return 0;
    return jie[x]*ni[x-y]%p*ni[y]%p;
}

void pre() {
    num[0]=1;
    for(int i=1;i<50;i++) {
        if(i&1) num[i]=-1;
        else num[i]=1;
    }
    jie[0]=ni[0]=1;
    for(int i=1;i<=size;i++) 
        jie[i]=jie[i-1]*i%MOD;
    ni[size]=ksm(jie[size],MOD-2,MOD);
    for(int i=size-1;i>=1;i--)
        ni[i]=ni[i+1]*(i+1)%MOD;
}

void dfs(int x) {
    if(x==k+1) {
        LL sum=0;
        for(LL i=1;i<=ches[0];i++)
            sum+=(need[ches[i]]+1);
        if(sum>m) return;
        ans=((ans+num[ches[0]]*calc(n+m-sum-1,m-sum,MOD)%MOD)%MOD+MOD)%MOD;
        return;
    }
    dfs(x+1); ches[++ches[0]]=x;
    dfs(x+1); ches[0]--;
}

【巨胖的輾轉相除】(Euclid.pas/c/cpp Time:1s Memory:256M)

【問題描述】

巨胖最近學完了輾轉相除法求最大公約數,即歐幾里得法求最大公約數之後,非常的開心,尤其是當他發現原來輾轉相除法的時間複雜度是O(logN) 的時候,更是喜不自勝。但是,雖然都是LogN ,當N給定的時候,一個數對(a,b)(1abN) 可能只要輾轉相除一次就算出最大公約數了,有的可能還要輾轉相除若干次。數對(a,b) 輾轉相除的次數定義爲(a,b) 其中有一項變爲0 的時候,產生了多少個不同的數對。例如:(3,5)(2,3)(1,2)(0,1) ,所以數對(3,5)輾轉相除的次數爲4。
現在給定一個N,求N中間輾轉相除次數最多的數對(a,b) 。對了,當滿足條件的(a,b) 有很多個的時候,選擇a最小的,若還有很多個,選擇b最小的。

【輸入】

輸入文件名爲Euclid.in。
輸入僅一行,一行一個正整數N。

【輸出】

輸出文件名爲Euclid.out。
輸出包含兩行,第一行一個正整數a,第二行一個正整數b。

【輸入輸出樣例】

Euclid.in
4 2

Euclid.out
3

【數據範圍】

對於20%的數據,N104
對於50%的數據,N1018
對於100%的數據,3N1012000

【題解】

這個題啊,值得抨擊,可以打表發現答案就是斐波那契數列的相鄰的兩項。

其實證明如下:

證明最優解是斐波那契數列相鄰兩項的方法:首先gcd(a,b)=1最優,因爲gcd(a,b)>1我們可以把a,b同時整除gcd(a,b),輾轉相除次數仍然相同,但是a,b更小。那麼輾轉相除的最終結果就是a1=0,b1=1。然後遞歸回去時,a,b的公式爲:a2=b1=1,b2=a1+b1*k(k>0)那麼當k=1時a2,b2最小,即a2=b1,b2=a1+b1,即斐波納契數列,問題得證。

知道了吧。但是隻能拿50分,那麼如何打100分呢?

加了個高精度即可,需要壓位,壓位,壓位!!!(代碼複雜度陡升)

【代碼】

#include<cstdio>
#include<cstring>
const int MAXN=12000;
typedef long long LL;
const LL MOD = 10000000000000000LL;
#define max(x,y) ((x)>(y)?(x):(y))

char num[MAXN];

struct BIGNUM {
    LL len,s[MAXN/10];
    BIGNUM () {
        memset(s,0,sizeof(s));
        s[1]=1;
        len=1;
    }
    BIGNUM operator = (const char*num){
        s[1]=0;
        len=(strlen(num)-1)/16+1;
        LL yu=strlen(num)%16-1,wei=len;
        for(LL i=0;i<=yu;i++)s[wei]=s[wei]*10+num[i]-'0';
        for(LL i=yu+1;i<len;i+=16){
            --wei;
            for(LL j=0;j<16;j++)
                s[wei]=s[wei]*10+num[i+j]-'0';
        }
        return *this;
    }
    BIGNUM operator + (const BIGNUM&num){
        BIGNUM c;
        c.s[1]=0;
        c.len=max(num.len,len);
        for(LL i=1;i<=c.len;i++){
            c.s[i+1]=(c.s[i]+s[i]+num.s[i])/MOD;
            c.s[i]=(c.s[i]+s[i]+num.s[i])%MOD;
        }
        if(c.s[c.len+1])c.len++;
        return c;
    }
    bool operator > (const BIGNUM&num) const{
        if(len!=num.len)return len>num.len;
        for(LL i=len;i>=1;i--)
            if(s[i]!=num.s[i])
                return s[i]>num.s[i];
        return false;
    }
    void out() {
        for(LL i=len;i>=1;i--){
            if(i==len) printf("%lld",s[i]);
                else printf("%016lld",s[i]);
        }
    }
};

BIGNUM tmp,a,b,c;

int main(){
    freopen("euclid.in","r",stdin);
    freopen("euclid.out","w",stdout);
    scanf("%s",num);
    tmp=num;
    c=a+b;
    while(!(c>tmp)){
        a=b;
        b=c;
        c=a+b;
    }
    a.out();putchar('\n');b.out();putchar('\n');
    return 0;
}

【巨胖的最大約數】(divisor.pas/c/cpp Time:1s Memory:256M)

【問題描述】

巨胖虐了無數道現哥出的約數的題目之後,開始對約數感興趣了。
給定一個範圍[1,N] ,巨胖想知道範圍內的約數最多的數是哪一個。如果有約數最多的有複數個,則輸出數值最小的。

【輸入】

輸入文件名爲Divisor.in。
輸入一個正整數N。

【輸出】

輸出文件名爲Divisor.out。
輸出一個正整數X,代表[1,N] 範圍內約數最多且最小的數。

【輸入輸出樣例】

Divisor.in
3

Divisor.out

2

【數據範圍】

對於30%的數據,保證有N105
對於100%的數據,保證有N109

【題解】

其實就是反素數啊啊啊。

鏈接自己戳:http://www.zhouyiguo.cc/2017/02/cogs-693-antiprime%e6%95%b0/

【代碼】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
LL n;
LL h[20]={30};
LL prime[]={0,2,3,5,7,11,13,17,19,23,29};
LL maxx=-1,ans;

void search(LL x,LL prod,LL sum);

int main() {
    freopen("divisor.in","r",stdin);
    freopen("divisor.out","w",stdout);
    scanf("%lld",&n);
    search(1,1,1);
    printf("%lld\n",ans);
    return 0;
}

inline void search(LL x,LL prod,LL sum) {
    LL t=prod,tot=0;
    for(LL i=1;i<=h[x-1];i++) {
        t=t*prime[x];
        if(t>n) {
            if(maxx<sum*(tot+1) or (maxx==sum*(tot+1)and(t/prime[x]<ans))) {
                maxx=sum*(tot+1);
                ans=t/prime[x];
            }
            return ;
        }
        tot++; h[x]=i;
        search(x+1,t,sum*(tot+1));
    }
    return ;
}

總結

哎,還得繼續努力啊啊啊!!!

發佈了39 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章