题目链接:https://codeforces.com/gym/102821/problem/J
题意:给你一个坐标k,每次从0开始走
每次有三个选择:选择1走一步,选择2走两步,选择3走三步
每次选第i个选择的时候,如果他没有被选过,那么就走i步
如果他被选过了的话就走上一次选择这个选择走过的步数+3步
问:到k的最小步数和到k有多少种方法
思路:我们把每次选择打表:
然后我们可以用一个二维数组v【i】【j】来预处理存选项i一共选了j次所走的路程
设选项1走了i次,选项2走了j次,选项3走了k次
我们只用列举一下i和j的路程,然后算出k
如果满足条件更新步数ans=min(ans,i+j+k)
然后对于每个满足的i j k,选择的方案是C(i+j+k,i)*C(j+k,j)
(在i+j+k个位置里选i个位置走选项1,在剩下的j+k个位置走选项2)
然后我们化简一下就是:
因为需要取模,然后我们就要用逆元求一下阶乘的倒数r【i!】数组,还需要求一下阶乘f【i】数组
由打表可以知道i,j,k不超过2600个,
而且最后是i+j+k,那么我们就开3*3000个数组
具体代码如下:
- #include<bits/stdc++.h>
- using namespace std;
- #define int long long
- typedef long long ll;
- int n,ans,con;
- const int N=3*3000,mod=1e9+7;
- int f[N],r[N];
- vector<int> v[5];
- int ksm(int a,int b,int p){
- int res=1%p;
- while(b){
- if(b&1)res=res*a%p;
- a=a*a%p;
- b>>=1;
- }
- return res;
- }
- void init(){
- for(int i=1;i<=3;i++){//预处理选项i选了j次走的路程
- v[i].push_back(0);//当选0次的时候走了0
- for(int j=1;;j++){
- int op=v[i][j-1]+(j-1)*3+i;
- if(op>1e7)break;//当大于1e7的时候剪枝
- v[i].push_back(op);
- }
- v[i].push_back(1e9); //最后加上个大整数是方便lower_bound查找
- }
- f[0]=r[0]=1;
- for(int i=1;i<N;i++){//处理阶乘
- f[i]=f[i-1]*i%mod;
- }
- for(int i=1;i<N;i++){//处理阶乘的逆元
- r[i]=ksm(f[i],mod-2,mod);
- }
- }
- signed main(){
- init();
- int t;
- scanf("%lld",&t);
- for(int p=1;p<=t;p++){
- ans=1e9;
- cin>>n;
- con=0;
- for(int i=0;v[1][i]<=n;i++){//列举选项1
- for(int j=0;v[1][i]+v[2][j]<=n;j++){//列举选项2
- int x=n-v[1][i]-v[2][j];//算选项3
- int k=lower_bound(v[3].begin() ,v[3].end() ,x )-v[3].begin() ;
- if(v[3][k]==x){//如果满足
- ans=min(ans,i+j+k);
- con=(con+f[i+j+k]*r[i]%mod*r[j]%mod*r[k]%mod)%mod;
- }
- }
- }
- printf("Case %lld: %lld %lld\n",p,ans,con);
- }
- return 0;
- }