2018 UESTC ACM Summer Training Team Selection (3) (赛后补题)

2018 UESTC ACM Summer Training Team Selection (3)
由于自己水平有限,补题补的都是感觉应该能做出来的题目。(包括水过去的和没做出来的)
由样例我们可以发现,9可以以为基础被拆分成3^2、3^1、3^0,这三种情况综合起来的总次数就是答案。

动规方程为dp[n][k](k表示最大的数不超过m^k),dp[n][k]=dp[n][k-1]+dp[n-m^k][k];
意思为数字为n且分离出的数字最大不超过m^k的种类等于:

1、完全没有m^k的n分出的种类(最大数不超过m^(k-1))
2、已经放了一个 m^k(有可能还能放一个所以数字为n-m^k,但最大数仍然不超过m^k,也许还能再放一个:比如9^3-1放了一个9^2是不是还能放一个9^2?当然9^3不可能放进去)

接下来是代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
using namespace std;
int quick_pow(int n,int k)  
{  
    int ans=1;  
    while(k)  
    {  
        if(k&1) ans*=n;  
        n*=n;  
        k>>=1;  
    }  
    return ans;  
}/*快速求幂法*/ 
int dp[10010][200];
int main()
{
	int T;
	int k,m,n;
	cin>>T;
	while(T--)
	{
		cin>>k;
		cout<<k;
		cin>>m>>n;
	
	int max_=0;/*记录最大的次数*/
	while(quick_pow(m,max_+1)<=n)max_++;/*+1可以省一步操作*/
	for(int i=0;i<=n;i++)
	for(int j=0;j<=max_;j++) 
	{
    	dp[i][j]=1;
    }
	for(int i=1;i<=max_;i++)
    {
    	for(int j=quick_pow(m,i);j<=n;j++)
    	{
   		  
		  dp[j][i]=dp[j][i-1]+dp[j-quick_pow(m,i)][i];
		  for(int k=1;k<=max_;k++)
		  {
  			dp[j][k]=dp[j][i];
  		  }/*这里事必须要加上的否则会少很多次数:
			dp[j][i]前面计算得到都是刚好用了这么多i得情况,比如dp[3][1]=2但是dp[3][2]没有被赋值而dp[3][2]同样等于dp[3][1]
			比如dp[12][2]=dp[12][1]+dp[3][2]:这里dp[3][2]没有重新赋值,所以答案会错误*/ 
    	}
    }
    printf(" %d",dp[n][max_]);
	}
} 

然后引用一下这位大牛得优化代码:滚动数组真的强。/我这份代码有可能TLE(比赛过后交不了题了QAQ不能确定)/
https://blog.csdn.net/qq_36398723/article/details/77304611

采取这样得方式就不需要考虑i没有刚好满足j需求得情况(见前面注释为什么少了一些次数)
#include <iostream>  
#include <algorithm>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
typedef long long llt;  
llt dp[10010];  
int quick_pow(int n,int k)  
{  
    int ans=1;  
    while(k)  
    {  
        if(k&1) ans*=n;  
        n*=n;  
        k>>=1;  
    }  
    return ans;  
}  
int main()  
{  
    int T;  
    scanf("%d",&T);  
    while(T--)  
    {  
        memset(dp,0,sizeof(dp));  
        int a,b,n;  
        scanf("%d%d%d",&a,&b,&n);  
        int max_pack=0;  
        //quick_pow(b,max_pack)表示能从n中划分出最大的数  
        while(quick_pow(b,max_pack+1)<=n) max_pack++;  
        for(int i=0;i<=n;i++) dp[i]=1;  
        //滚动数组优化  
        for(int i=1;i<=max_pack;i++)  
        {  
            int p=quick_pow(b,i);  
            for(int j=p;j<=n;j++)  
            {  
                dp[j]=dp[j]+dp[j-p];  
            }  
        }  
        printf("%d %lld\n",a,dp[n]);  
    }  
    return 0;  
}  


这道题。
因为自己独立思考得能力还是太差得缘故。想到了正向往下推log n得复杂度可以接受,但是没想到反向推回来.....
正向往下推是越推越多,写了一份代码就不写出来了,到后面程序都算不出来了(也有可能数组装不下来反正程序不得过)
但是回推就能保证走最少的步数。QAQ很好想但是比赛花了快两个小时一直写不出来。叹气。应该蛮简单得一道题。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
using namespace std;
struct Node
{
	int a;
	int b;
};
Node A={1,1};
Node fun(int n)
{	
	if(n==1)return A;
	else
	{
		Node T=fun(n/2);
		if(n%2==0)
		{
		    Node B;
		    B.a=T.a;
		    B.b=T.a+T.b;
		    return B;
		}
		else
		{
			Node B;
			B.a=T.a+T.b;
			B.b=T.b;
			return B;
		}
	}
}
int main()
{
	int T,k,n;
	cin>>T;
	while(T--)
	{
		cin>>k>>n;
		cout<<k;
		Node C=fun(n);
		printf(" %d/%d",C.a,C.b);
		printf("\n");
	}
} 

值得注意的是刚刚手写了一下代码,fun函数中我定义Node T,一开始没有这一步,所有的T都是fun(n/2),对于每一个这个全都进行一次回退(其实用T回退一次就够了)然后因为这个小失误倒置最后一个数据 1431655765算了好几分钟都没算出来。
这道题我想到的思路是首先枚举出第一个下降序列,再枚举出第二个,再枚举第三个,接着对于每个枚举出来的情况,把剩下的上升序列插入进去(注意保证插入过后不会产生新的下降序列)例如12345,k=2,枚举21、31、41、51、32....出来之后选第一个,再选第二个(数字不能重复),比如选了21、43接下来就是把5插入到*21*43*中,保证合法性的情况只有最右边能插入。
这样的思路算出结果应该是没问题...但是一直想不出来怎么实现。

然后上网查了一下...结果是动态规划,果然自己DP学的还是太差了...
参考了一下:https://blog.csdn.net/lwgkzl/article/details/76033662
dp方程中dp[i][j]表示数字为1~i时有j个下降序列的排列种数。然后就是写的超级棒的dp方程(也可能我太菜了。)

对于dp[i][j],从上一步的状态1:dp[i-1][j]意味着下降序列已经定好,这个时候由于i对于之前的1~i-1必然是大于的,所以放在出最后一位之外的位置(放在最后一位就是一种情况啦)一般是会使下降序列增加的,除非放在下降序列中,比如1 3 2 7 4 5 6这个序列,放8的时候如果放在3、2或者7、4中会变成3、8、2和7、8、4。稍微证明一下对于下降序列m+k,m,(m+k<=i-1),i插入到中间之后m+k,i必然升序,i,m必然降序,如果放在其他位置(最后一个除外)i,m必然形成新的下降序列。
dp[i][j]=状态1(dp[i-1][j]*(j+1))+状态2

从上一步的状态2:dp[i-1][j-1]从上面的分析中我们可以看出j+1的位置不会改变下降序列,而事实上一旦改变必然增加一个,那么总共i+1的位置,一共有i+1-(j+1)的位置会增加一个。
所以dp方程为:dp[i][j]=dp[i-1][j-1]*(j+1)+dp[i-1][j]*(i-j);
#include <cstdio>    
#include <cstring>    
#include <cmath>    
#include <iostream>    
#include <queue>  
#include <set>  
#include <string>  
#include <stack>  
#include <algorithm>  
#include <map>  
using namespace std;    
typedef long long ll;  

int dp[110][110];
int main()  
{  
     int T,k,n,ans;
     cin>>T;
     while(T--)
     {
     	memset(dp,0,sizeof(dp));
     	cin>>k>>n>>ans;
     	cout<<k;  	 
        dp[1][0]=1;
        dp[1][1]=0;
        for(int i=2;i<=n;i++)
        {
        	dp[i][0]=1;
        	for(int j=1;j<=ans;j++)
        	{
	        	dp[i][j]=dp[i-1][j]*(j+1)+dp[i-1][j-1]*(i-j);
	        	dp[i][j]%=1001113;
	        }
        }

        cout<<" "<<dp[n][ans];
        printf("\n");
	 }
}  
今晚有CF,加油。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值