数学测试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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章