【巨胖的技能組合】(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%的數據,保證有
對於50%的數據,保證有
對於80%的數據,保證有
對於100%的數據,保證有
【題解】
大意是要釋放M次技能,有N種技能可以釋放,其中K種技能釋放次數有限,其他技能釋放次數無限,求釋放技能的方案數。
20分算法:
直接搜素,複雜度自己算
50分&80分算法:
其實可以考慮動歸,F[i,j]表示已經考慮了前i個技能,已經釋放了j個技能的方案數。裸轉移方程就是
100分算法:
動歸已經不好優化了,觀察題目別的性質:帶限制的技能數量很小。我們要求所有限制技能的釋放次數在限制次數範圍內。一種方法容斥原理求解:首先設N種技能的使用次數分別爲
【代碼】
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)
【問題描述】
巨胖最近學完了輾轉相除法求最大公約數,即歐幾里得法求最大公約數之後,非常的開心,尤其是當他發現原來輾轉相除法的時間複雜度是
現在給定一個N,求N中間輾轉相除次數最多的數對
【輸入】
輸入文件名爲Euclid.in。
輸入僅一行,一行一個正整數N。
【輸出】
輸出文件名爲Euclid.out。
輸出包含兩行,第一行一個正整數a,第二行一個正整數b。
【輸入輸出樣例】
Euclid.in
4 2
Euclid.out
3
【數據範圍】
對於20%的數據,
對於50%的數據,
對於100%的數據,
【題解】
這個題啊,值得抨擊,可以打表發現答案就是斐波那契數列的相鄰的兩項。
其實證明如下:
證明最優解是斐波那契數列相鄰兩項的方法:首先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)
【問題描述】
巨胖虐了無數道現哥出的約數的題目之後,開始對約數感興趣了。
給定一個範圍
【輸入】
輸入文件名爲Divisor.in。
輸入一個正整數N。
【輸出】
輸出文件名爲Divisor.out。
輸出一個正整數X,代表
【輸入輸出樣例】
Divisor.in
3
Divisor.out
2
【數據範圍】
對於30%的數據,保證有
對於100%的數據,保證有
【題解】
其實就是反素數啊啊啊。
鏈接自己戳: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 ;
}
總結
哎,還得繼續努力啊啊啊!!!