2018 UESTC ACM Summer Training Team Selection (3)
由于自己水平有限,补题补的都是感觉应该能做出来的题目。(包括水过去的和没做出来的)
![](https://i-blog.csdnimg.cn/blog_migrate/52b1dfef19fc626dc51c3503abdd0f79.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dabd87bcbdfc3e1efd197faae5342941.png)
由样例我们可以发现,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;
}
![](https://i-blog.csdnimg.cn/blog_migrate/3ed90dc101b8a9d7d5ef0a7e47c354cc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/50fd735098b3130b7a9bcc966897f713.png)
这道题。
因为自己独立思考得能力还是太差得缘故。想到了正向往下推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算了好几分钟都没算出来。
![](https://i-blog.csdnimg.cn/blog_migrate/8f727f0d35fea5f54100c05dd1c10ea4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c4715d305b1c8ae53118b0f448418e88.png)
这道题我想到的思路是首先枚举出第一个下降序列,再枚举出第二个,再枚举第三个,接着对于每个枚举出来的情况,把剩下的上升序列插入进去(注意保证插入过后不会产生新的下降序列)例如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,加油。