【巨胖的技能组合】(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,M≤5
对于50%的数据,保证有
N,M≤100
对于80%的数据,保证有
N,M≤1000
对于100%的数据,保证有
N,M≤105
,
K≤Min(N,15)
【题解】
大意是要释放M次技能,有N种技能可以释放,其中K种技能释放次数有限,其他技能释放次数无限,求释放技能的方案数。
20分算法:
直接搜素,复杂度自己算
50分&80分算法:
其实可以考虑动归,F[i,j]表示已经考虑了前i个技能,已经释放了j个技能的方案数。裸转移方程就是 F[i][j]=∑jk=max{0,j−l[i]}F[i−1][k] ,l[i]表示第i个技能的释放次数,如果没有限制则为无穷大。(50分),又因为 F[i][j] 每次取的都是连续一段的和,那么这可以用前缀和维护。时间复杂度可以降一维。(80分)
100分算法:
动归已经不好优化了,观察题目别的性质:带限制的技能数量很小。我们要求所有限制技能的释放次数在限制次数范围内。一种方法容斥原理求解:首先设N种技能的使用次数分别为 x1,x2,x3…xn ,那么由题意可以得到式子 x1+x2+x3⋯+xn=M ,然后要求前k个元的解集分别在0到某个数,其他都为非负整数,求它的解的数量。先求出至少0个限制技能使用过度的方案数:即N种元素可重选M个的可重组合。即 CN+M−1N 。然后求出至少1个限制技能使用过度的方案数,假设这个过度使用的技能标号为I,则原式变为 x1+x2+(xi+li+1)…xn=N 即 x1+x2+⋯+xi+⋯+xn=N−li−1 。仍然可以可重组合。至少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)(1≤a≤b≤N)
可能只要辗转相除一次就算出最大公约数了,有的可能还要辗转相除若干次。数对
(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%的数据,
N≤104
。
对于50%的数据,
N≤1018
。
对于100%的数据,
3≤N≤1012000
。
【题解】
这个题啊,值得抨击,可以打表发现答案就是斐波那契数列的相邻的两项。
其实证明如下:
证明最优解是斐波那契数列相邻两项的方法:首先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%的数据,保证有
N≤105
对于100%的数据,保证有
N≤109
【题解】
其实就是反素数啊啊啊。
链接自己戳: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 ;
}
总结
哎,还得继续努力啊啊啊!!!