PAT (Advanced Level) Practice 1103
1. 题目大意
给出三个正数
N
,
P
,
K
N,P,K
N,P,K,其中范围分别是
N
≤
400
,
K
≤
N
,
1
<
P
≤
7
N\leq400,K\leq{N},1<P\leq7
N≤400,K≤N,1<P≤7
找出满足下列条件的序列,
n
[
1
]
…
n
[
K
]
n[1]…n[K]
n[1]…n[K]
- N = n [ 1 ] P + . . . n [ K ] P N = n[1]^P + ... n[K]^P N=n[1]P+...n[K]P
- 多组解保证 m a x ( n [ 1 ] + … n [ k ] ) max(n[1]+…n[k]) max(n[1]+…n[k])
- 若 m a x ( n [ 1 ] + … n [ k ] ) max(n[1]+…n[k]) max(n[1]+…n[k])相同则保证序列是最大字典序的
2. 解法
我们通过题目给出的样例思考,169,5,2,首位最大的数字应该是12,怎么算出来的呢?因为5个坑位,不能有0,题目要求保证序列最后是最大字典序的,所以我们可以规定序列是一个单调不增的,这样又限制了第一个坑位的下界。比如169,5,2,需要给后面四个留数值,所以 a [ 1 ] 2 < = 169 − 4 a[1]^2<=169-4 a[1]2<=169−4,并且 5 × a [ 1 ] 2 > = 169 5{\times}a[1]^2>=169 5×a[1]2>=169,如此一来, a [ 1 ] a[1] a[1]的范围就被限制在了6和12之间。题目给出的P的范围是2到7,实际上情况不是很多,可以考虑使用dfs枚举所有情况。
如何确定dfs的范围呢?
我们定义函数dfs(depth,remain,thread),其中,depth代表dfs进行到了序列的第几位,remain表示n减去之前序列的P次方剩余的数量,thread表示上界,由于我们枚举的是不上升序列。
对于上界,由于当前遍历到的深度为depth,即在当前位之后还有k-depth位,对于这k-depth个坑位至少每个要留下个1,即k-depth个 1 p 1^p 1p,所以要找到上界 u p up up使得
u p p < = n ( u p + 1 ) p > n up^p<=n \quad (up+1)^p>n upp<=n(up+1)p>n
对于下界,我原先的代码设置为了1,但是这样会导致一些极端数据严重超时,比如400 200 2。究其原因,下界设置为1也有很多不必要的搜索过程。于是乎考虑dfs的剪枝。由于我们需要给出的是不上升的序列,我们可以增加如下剪枝
if ((k-depth+1)*pow(thread,p)<remain) return;
thread表示上界,即如果之后所有的坑位都使用最大上界依然无法填补未达到的remain之后返回,增加了这个剪枝之后唯一超时的点跑过去了。
#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000
int n,k,p;
int a[MAXN];
int ans[MAXN],maxx;
int re;
int findup(int n,int p)
//寻找上界
{
int x=0;
while (pow(x,p)<=n)
{
if ((pow(x,p)<=n)&&pow(x+1,p)>n) return x;
x++;
}
}
int findlow(int n,int k,int p)
//寻找首位下界
{
int x=0;
while (k*pow(x,p)<n)
{
if ((k*pow(x,p)<n)&&(k*pow(x+1,p)>=n)) return x+1;
x++;
}
}
void dfs(int depth,int remain,int thread)
{
if ((k-depth+1)*pow(thread,p)<remain) return;//必要的剪枝
if (depth==k)
{
//达到搜索深度了
for (int i=thread;i>=1;i--)
//找到pow(i,p)刚好填补remain
if (remain==pow(i,p))
{
a[depth]=i;
int tot=0;
for (int i=1;i<=k;i++) tot+=a[i];
// for (int i=1;i<=k;i++) printf("%d ",a[i]);
// cout<<endl;
if (tot>maxx)
{
maxx=tot;
for (int i=1;i<=k;i++) ans[i]=a[i];
}
return;
}
return;
}
int y=k-depth;
int x=remain-y;
int up=min(thread,findup(x,p));//取计算出的上界和在这坑位之前的上界的最小值
for (int i=up;i>=1;i--)
{
a[depth]=i;
int re=remain-pow(i,p);
//i作为新的上界,继续搜索
dfs(depth+1,re,i);
}
}
int main()
{
scanf("%d%d%d",&n,&k,&p);
if (k==1)
{
int x=0;
while (pow(x,p)<n) x++;
if (pow(x,p)==n)
{
printf("%d = ",n);
printf("%d^%d",x,p);
}
else printf("Impossible");
return 0;
}
int x=n-(k-1);
int up=findup(x,p);
int low=findlow(n,k,p);
for (int i=up;i>=low;i--)
{
re=n;
a[1]=i;
re-=pow(i,p);
dfs(2,re,i);
}
if (ans[1])
{
printf("%d = ",n);
for (int i=1;i<k;i++)
printf("%d^%d + ",ans[i],p);
printf("%d^%d",ans[k],p);
}
else printf("Impossible");
return 0;
}