题目链接:2018SCUACM Training 3 二分/背包/并查集
昨天补完上周欠的training 2,今天来补training 3了。
A - Bone Collector
HDU - 2602已知N个糖果的重量和价值. 我们有一个口袋, 最多可以装V重量的糖果. 问口袋最多能放多少价值的糖果进去? Input
对每一组数据, 输出口袋最终可以放进去糖果的价值.
Sample Input1 5 10 1 2 3 4 5 5 4 3 2 1Sample Output
14
最简单的01背包问题。
普通背包问题(物品可任意分割)是一个贪心问题,按性价比从高到低选
但是01背包问题用贪心无法解决
01背包问题是一个经典的DP问题,即动态规划
声明一个数组dp[][],其中dp[i][j]表示的是在第1个到第i个物品中,消耗最多j的容量,可以获得的最大的价值(dp数组的意义不唯一,但状态转移方程都是类似的)
则当我们知道dp[i-1]时,想要知道dp[i],也就是在dp[i-1]中进一步考虑向其中添加第i个物品
故状态转移方程为:dp[i][j] = max(dp[i-1][j], dp[i-1][j-cost[i]] + value[i]);分别对应j的消耗中不拿第i个物品,或拿第i个物品
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 1005
#define maxv 1005
using namespace std;
long long max(long long a, long long b)
{
return a > b ? a : b;
}
long long cost[maxn],value[maxn],dp[maxn][maxv];
int main()
{
int T,n,v,i,j;
scanf("%d",&T);
while(T--)
{
memset(dp,0,sizeof(dp));
scanf("%d%d",&n,&v);
for(i = 1; i <= n; i++)
scanf("%lld",&value[i]);
for(i = 1; i <= n; i++)
scanf("%lld",&cost[i]);
for(i = 1; i <= n; i++)
for(j = 0; j <= v; j++)
{
//dp[i][j]:前i个物品容量最多为j时价值的最大值
dp[i][j] = max(dp[i][j], dp[i-1][j]);//不选第i个物品
if(j >= cost[i])//如果可以选第i个物品
dp[i][j] = max(dp[i][j], dp[i-1][j-cost[i]]+value[i]);
}
printf("%lld\n",dp[n][v]);
}
return 0;
}
B - The Suspects
POJ - 1611100 4 2 1 2 5 10 13 11 12 14 2 0 1 2 99 2 200 2 1 5 5 1 2 3 4 5 1 0 0 0Sample Output
4 1 1
问题要判断最终和0号学生一组的同学数目,典型的并查集问题
并查集,是支持以下两种操作的数据结构:合并 :将两个元素合并为一组。查询:返回两个元素是否为一组
首先,我们认为第i个同学是归属于第i组的,即任意两个同学都是不同组的。
然后我们读入每个小组的数据,不断的合并每两个同学所在的组(而不是合并两个同学,因为他们可能已经归属于某个组)
最后就只要扫一遍数出和0号同学一组的同学数目即可。
其实并查集可以看成是一个森林,合并即在森林的两棵树中连一条边使他们连通,查询即判断两个节点所在的树的根节点是否相同。由于并查集只能判断两个元素是否在同一个集合中,而不能判断一个集合究竟有哪些元素,即访问一棵树时只能由下到上访问,而不能从上到下,故减小树的高度会很大程度上提高查询效率。
如何减小树的高度呢?在并查集中,我们只关心某个节点对应的根节点是哪个,而不会关心其父亲或其爷爷是哪个。所以我们在每次取根节点的操作时,都直接将取到的结点连到根节点上就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 30005
int s[maxn];
int root(int x)// find the root of s[x]
{
if(s[x] == x)
return x;
return s[x] = root(s[x]);
}
int main()
{
int n,m,i,k,t,last,cnt;
while(1)
{
scanf("%d%d",&n,&m);
if(n == 0 && m == 0)//multiple data
break;
for(i = 0; i <= n; i++)//initialize
s[i] = i;
cnt = 0;
for(i = 0; i < m; i++)
{
scanf("%d",&k);//number of groups
last = -1;
while(k--)
{
scanf("%d",&t);
if(last == -1)//the first man of the group
last = t;
else
s[root(t)] = root(last);//unite people of thte group
}
}
t = root(0);
for(i = 0; i < n; i++)//count the answer
if(root(i) == t)
cnt++;
printf("%d\n",cnt);
}
return 0;
}
C - 4 Values whose Sum is 0
POJ - 27856 -45 22 42 -16 -41 -27 56 30 -36 53 -37 77 -36 30 -75 -46 26 -38 -10 62 -32 -54 -6 45Sample Output
5Hint
这题是在4个数组中分别取一个数,判断有多少个组合能满足四个数之和为0.
这题的简化版——判断【能否】在4个数组中取一个数,使得他们的和为某个值——在《挑战程序设计竞赛》这本书中详细讨论过,而且在书比较靠前的部分
我们先来讨论简化版的问题。因为暴力枚举4个数组的组合复杂度为O(n^4),基本是不用考虑的方法。有一种优化是:先在3个数组中取一个数,判断要凑出sum剩余的数在第四个数组中是否存在。这时问题就变成了在数组中判断某个数是否存在的问题,就可以考虑排序后二分,则优化到O(n^3logn),但还不够。
基于同样的思想,我们可以枚举两个元素,再在剩余的两个数组中判断所需要的另外两个元素是否存在。显然判断两个数组中是否存在某个数是困难的,所以我们可以预处理出这两个数组可能凑出来的所有的和,这个枚举过程是O(n^2)的,得到新的n*n个元素,然后在这个数组中二分。
这种方法枚举两个数组的和复杂度为O(n^2),二分预处理的排序过程需要O(n²logn),枚举前两个数组的两个元素复杂度为O(n²),单次查询的复杂度为O(logn),故总体复杂度为O(n²logn),是一个比较能接受的量级。
但这题我们不但要判断能否凑出某个和,还要求出凑出这个和的方案数。所以在每次二分时,我们不但要判断在数组中是否存在这个元素,还要对这个元素进行计数。计数可以使用STL库中的upper_bound和lower_bound函数,但这里我采取的是一种比较笨的办法,不过我用的方法并不会在复杂度上劣化这个程序。
#include<cstdio>
#include<algorithm>
#define maxn 4005
using namespace std;
int a[maxn],b[maxn],c[maxn],d[maxn];
int sum_cd[maxn*maxn];
int n;
int bin_search(int num)
{
int l = 0, r = n*n;
while(l < r)
{
int m = (l+r) / 2;
if(sum_cd[m] == num)
return m;
if(sum_cd[m] < num)
l = m+1;
else r = m;
}
return -1;
}
int cd_count(int index)
{
int l, r, key;
l = r = index;
key = sum_cd[index];
while(r+1 < n*n && sum_cd[r+1] == key)
r++;
while(l-1 >= 0 && sum_cd[l-1] == key)
l--;
return r-l+1;
}
int main()
{
int i,j,t,cnt;
while(scanf("%d",&n) != EOF)
{
cnt = 0;
for(i = 0; i < n; i++)
scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
for(i = 0; i < n; i++)
for(j = 0; j < n; j++)
sum_cd[i*n+j] = c[i]+d[j];
sort(sum_cd,sum_cd+n*n);
for(i = 0; i < n; i++)
for(j = 0; j < n; j++)
{
t = bin_search(-a[i]-b[j]);
if(t != -1)
cnt += cd_count(t);
}
printf("%d\n",cnt);
}
return 0;
}
D - Wireless Network
POJ - 2236在处理网络修复的过程中,工作人员们在任何一个时刻,可以执行两种操作:维修一台电脑,或测试两台电脑是否能够通信。请您找出全部的测试操作。
1. "O p" (1 <= p <= N),表示维护电脑 p 。
2. "S p q" (1 <= p, q <= N),表示测试电脑 p 和 q 是否能够通信。
输入不超过 300000 行。
4 1 0 1 0 2 0 3 0 4 O 1 O 2 O 4 S 1 4 O 3 S 1 4示例输出
FAIL SUCCESS
这题需要做的是两个操作:修复计算机;判断两台计算机之间能否通信。
显然是个并查集问题。修复计算机对应在若干个结点之间连一条边,判断能否通信对应判断两个结点的根节点是否相同。
预处理一下任意两台计算机之间的距离,当计算机修复时,将其在范围d内的已修复的所有电脑与之连一条边,之后查询直接检查两个结点的root是否相同就行了。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 1005
#define maxd 20005
using namespace std;
typedef struct
{
int pre,x,y;
} node;
node a[maxn];
int fix[maxn];
int dist[maxn][maxn];
double dis_squ(node a, node b)
{
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int root(int index)
{
if(a[index].pre == index)
return index;
return a[index].pre = root(a[index].pre);
}
int main()
{
int n,d;
memset(dist,0,sizeof(dist));
memset(fix,0,sizeof(fix));
scanf("%d%d",&n,&d);
for(int i = 0; i <= n; i++)
a[i].pre = i;
for(int i = 1; i <= n; i++)
scanf("%d%d",&a[i].x, &a[i].y);
getchar();
for(int i = 1; i <= n; i++)
for(int j = i+1; j <= n; j++)
dist[i][j] = dist[j][i] = dis_squ(a[i],a[j]);
char c;
while(scanf("%c",&c) != EOF)
{
if(c == 'O')
{
int p;
scanf("%d",&p);
while(getchar() != '\n');
fix[p] = 1;
for(int i = 1; i <= n; i++)
if(fix[i] && dist[i][p] <= d*d)
a[root(i)].pre = root(p);
}
else if(c == 'S')
{
int p,q;
scanf("%d%d",&p,&q);
while(getchar() != '\n');
printf(root(p) == root(q) ? "SUCCESS\n":"FAIL\n");
}
}
return 0;
}
E - The Frog's Games
HDU - 4004青蛙举行了运动会,要求青蛙跳跃小河。河流宽度为 L (1<= L <= 1000000000). 河里会有 n 个石头沿着垂直于河岸的直线排成一排,青蛙以跳到石头上,然后再次跳跃。青蛙最多能够跳 m 次;现在青蛙们想知道他们最少应该有多大的跳跃能力才能够到达河对岸?
Input多组数据;
每行输入 L,n,m;
接下来输入n个数,代表第 i 个石头距离开始位置的距离,两个石头不可能出现在一起。
Output输出一个数,代表至少需要的跳跃距离(最小的最大跳跃能力);
Sample Input6 1 2 2 25 3 3 11 2 18Sample Output
4 11
这题是求的“最小的最大跳跃能力”,即求“最小值的最大值”。对于“最小的最大”和“最大的最小”这一类问题,我们常常运用二分来解决,将求值问题转换为判定问题。即判断当最大跳跃能力为mid时,能否满足题目条件。
问题已经转换为判定问题之后,这题就是一个再简单不过的模拟题了。我们只要模拟一下青蛙跳的过程,判断能否到达终点即可。即,当当前位置为cur时,下一步要跳到尽可能接近cur+mid位置的石头上,这样重复m次,如果在m次以内跳到了终点,即mid成立,否则mid不成立。最后输出最小值即可。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 500005
#define maxm 500005
using namespace std;
int l,n,m;
int a[maxn];
int isAccess(int dis)//每次跳的最大距离为dis时能否到达终点
{
if(dis < a[0])
return 0;
int cur = 0;//当前位置
int time = m;//跳跃次数
while(time--)
cur = *(upper_bound(a,a+n+1,cur+dis)-1);
return cur >= l;
}
int main()
{
int i,mid_dis,max_dis,min_dis,dis;
while(scanf("%d%d%d",&l,&n,&m) != EOF)
{
dis = max_dis = l;//最好的情况,每次跳的距离为l(或大于l),跳一次就能跳到对岸
min_dis = 1;//最坏的情况,每次跳的距离为1,跳l次才能跳到对岸
for(i = 0; i < n; i++)
scanf("%d",&a[i]);
a[n] = l;
sort(a,a+n+1);
while(max_dis >= min_dis)
{
mid_dis = (max_dis+min_dis)/2;
if(isAccess(mid_dis) == 0)//每次最多跳mid_dis不能到达终点
min_dis = mid_dis+1;//最少也要跳mid_dis+1才能到达
else//每次最多跳mid_dis可以到达
{
if(dis > mid_dis)//更新dis
dis = mid_dis;
max_dis = mid_dis-1;//找比dis小的范围内还有没有能到达的
}
}
printf("%d\n",dis);
}
return 0;
}
F - FATE
HDU - 215910 10 1 10 1 1 10 10 1 9 1 1 9 10 2 10 1 1 2 2Sample Output
0 -1 1
这题乍一看是一个背包问题,只是相对01背包问题来说,它每种物品可以拿多个,相对完全背包问题来说,它有拿的总数的限制。
最重要的是,它所求的不是我们获得的最大经验值,而是剩余的最大忍耐值。
按照我们背包问题的dp思路,dp[i][j]代表的是前i只怪在j的忍耐度下能获得的最多的经验,显然这个dp策略是不可行的。所以我们不能把它当做一个背包问题来看。而要改变一下dp策略:dp[i][j]代表在i的忍耐度下,杀j只怪能够获得的最大经验值。这样最满足dp[i][s]>=所需经验值的i即为升级所需的最小忍耐度。
这时我们来研究状态转移方程,在dp[i][j]的状态下,杀一只第k种怪物对应的是i减小b[k],j减小1的状态,而k种怪物都有可能被杀,所以要遍历一遍怪物,即dp[i][j] = max(dp[i][j], dp[i-b[k]][j-1] + a[k]),k需要我们遍历
这题看到答案还是比较简单的,但是自己想的话emmmm,反正我没想出来,就算是现在在回顾总结,不看自己以前的代码也总结不出来。。。果然还是做的太少了啊
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
int n;//升最后一级还需要的经验值
int m;//剩余的忍耐度
int k;//怪的种数
int s;//最多杀的怪的个数
int a[105];//杀掉第i只怪可以获得的经验值
int b[105];//杀掉第i只怪会减少的忍耐度
int dp[105][105];//dp[i][j]表示在i的忍耐度下杀j只怪能得到的最多的经验
int main()
{
while(scanf("%d%d%d%d",&n,&m,&k,&s) == 4)
{
memset(dp,0,sizeof(dp));
for(int i = 1; i <= k; i++)
scanf("%d%d",&a[i],&b[i]);
int flag = 1;
int ans = -1;
for(int i = 1; i <= m && flag; i++)//遍历忍耐度
for(int j = 1; j <= s && flag; j++)//遍历杀的怪物数
for(int p = 1; p <= k && flag; p++)//遍历k种怪物
{
if(i >= b[p])//忍耐度大于第p个怪物的消耗
dp[i][j] = max(dp[i][j], dp[i-b[p]][j-1]+a[p]);
if(dp[i][j] >= n)
{
ans = m - i;
flag = 0;
}
}
printf("%d\n",ans);
}
return 0;
}
G - 食物链
POJ - 1182现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5Sample Output
3
这个题目我第一次见到是在《挑战程序设计竞赛》中见到的,题目难度挺大的,反正我现在也没自信一眼能写出来,甚至稍稍做一点变形也不一定能马上相除策略,当时也是看了很久很久才想清楚思路。
首先每只怪物只能归于3类,我们把第i只怪物属于第j类,1<=i<=n,1<=j<=3,定义为事件P[i][j],则n只怪物一共对应有3n个事件
【同样的i对应的3个事件是互斥的】,【而不同的的事件之间是互相独立的】
我们假设现在有第i1和i2只动物是同类的,也就是说P[i1][j]和P[i2][j]是同时发生的,那我们就可以把这两个事件合并。由于j有3个取值,则合并操作要进行3次
如果第i1只动物要吃第i2只动物,那么就代表P[i1][1]和P[i2][2],P[i1][2]和P[i2][3],P[i1][3]和P[i2][1]这3组事件是分别同时发生的,我们同样只需要合并这3组事件即可。
但是合并之前,我们还要判断这条命令是否和之前的命令冲突。还记得前几行说的“同样的i对应的3个事件是互斥的”吗?这就是我们判断命令是否冲突的唯一标准。如果我们要把两个事件合并,前提就是一个事件在之前没有和另一个事件的互斥事件合并。
比如掷骰子的事件,设A:得到的数小于等于3,B:得到的数是奇数,如果A和B同时发生,即我们要准备合并A和B,那么前提就是A在之前没有和B的互斥事件合并,即之前没有认定过“得到的数是偶数”。
回到开始的问题,在这个问题中,比如说我们要合并P[i1][1]和P[i2][2],那么我们只要保证P[i1][1]之前没有和p[i2][1]以及P[i2][3]合并过即可说明这条命令是有效的。
为什么不用反过来判断P[i1][1]以及P[i1][2]是否和P[i2][1]合并过呢?因为之前说了,每次合并操作都涉及到3个合并,而这3个合并是每次在2个固定的i1和i2中取了两个j来合并,所以如果i1的1个事件与i2的3个事件都没有合并过,那就说明了i1和i2之间没有进行过合并操作。如果这段看不懂的话,不妨加上这两个判断条件,并不会影响最终结果。
分析到这里,我们就知道了:X和Y是同类,对应合并P[X][1]和P[Y][1],P[X][2]和P[Y][2],P[X][3]和P[Y][3]这三个事件。X吃Y,对应合并P[X][1]和P[Y][2],P[X][2]和P[Y][3],P[X][3]和P[Y][1],判断一条命令是否为真,只需要判断即将合并的两个结点是否互斥,也就是判断事件1是否已经与事件2的互斥事件合并过。
我认为我还是尽可能的把这题的思路讲清楚了吧(至少比《挑战》上清楚多了)。如果还不懂我也没办法了。说了那么多也离不开两个操作:合并和判断,所以并查集就不多说了。在程序里没开到二维数组,只是把动物i对应的3个事件写作了i,i+n,i+2n的3个数组单元,除此之外都和以上的思路差不多。既然算法上已经清楚了代码上就简单多了。
不过群里好像有另一种思路的方法emmmm。。。可能因为太懒我也没细看,基本思路还是并查集,好像是在并查集中不但存下了父亲是谁,而且还用数字来储存每个动物与根节点之间的关系。。。没细看所以可能说错了,但我还是贴出来给读者自己研究。。。
我的代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a[150005];
int root(int index)
{
if(a[index] == index)//index指向a[index],a[index]指向自己,则不需要更新
return a[index];
return a[index] = root(a[index]);
}
int main()
{
int n,k,ans;
int i,d,x,y;
scanf("%d%d",&n,&k);
ans = 0;
for(i = 0; i <= 3*n; i++)//并查集初始化
a[i] = i;
while(k--)
{
scanf("%d%d%d",&d,&x,&y);
if(d != 1 && d != 2)
ans++;//命令格式错误
else if(x < 1 || x > n || y < 1 || y > n)
ans++;//下标错误
else
{
if(d == 1)//x和y是同类
{
if(root(x) == root(y+n) || root(x) == root(y+2*n))
ans++;
else
{
a[root(x)] = root(y);
a[root(x+n)] = root(y+n);
a[root(x+2*n)] = root(y+2*n);
}
}
else//d==2,x吃y
{
if(root(x) == root(y) || root(x) == root(y+2*n))//若已经有xy同类或x被y吃
ans++;
else
{
a[root(x)] = root(y+n);
a[root(x+n)] = root(y+2*n);
a[root(x+2*n)] = root(y);
}
}
}
}
printf("%d\n",ans);
return 0;
}
群里另一种思路的代码:
#include<cstdio>
const int N=50001;
int p[N],r[N],n;
int findset(int x)
{
if(x!=p[x])
{
int fx=findset(p[x]);
r[x]=(r[x]+r[p[x]])%3;
p[x]=fx;
}
return p[x];
}
bool Union(int d,int x,int y)
{
int fx=findset(x),fy=findset(y);
if(fx==fy)
{
if((r[y]-r[x]+3)%3!=d)return 1;
else return 0;
}
p[fy]=fx;
r[fy]=(r[x]-r[y]+d+3)%3;
return 0;
}
int main()
{
int k,count,i,d,x,y;
scanf("%d%d",&n,&k);
count=0;
for(i=1;i<=n;i++)p[i]=i,r[i]=0;
while(k--)
{
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n||(x==y&&d==2)){count++;continue;}
if(Union(d-1,x,y))count++;
}
printf("%d\n",count);
return 0;
}
H - A very hard mathematic problem
HDU - 4282Find three positive integers X, Y and Z (X < Y, Z > 1) that holds
X^Z + Y^Z + XYZ = K
where K is another given integer.
Here the operator “^” means power, e.g., 2^3 = 2 * 2 * 2.
Finding a solution is quite easy to Haoren. Now he wants to challenge more: What’s the total number of different solutions?
Surprisingly, he is unable to solve this one. It seems that it’s really a very hard mathematic problem.
Now, it’s your turn.
For each case, there is only one integer K (0 < K < 2^31) in a line.
K = 0 implies the end of input.
Output Output the total number of solutions in a line for each test case.
Sample Input
9 53 6 0Sample Output
1
1
0
Hint
9 = 1^2 + 2^2 + 1 * 2 * 2 53 = 2^3 + 3^3 + 2 * 3 * 3
题目就是给定K,问有多少组XYZ满足题目的方程。
解方程的问题,我们也可以想到二分法。因为方程左边的函数,当确定两个变元时,都是关于第三个变元的增函数。单调性确定刚好满足二分的前提。
这时候将哪个东西二分就是我们要考虑的问题了。我们当然希望将范围越大的采用二分,范围小的直接遍历就行了。所以我们要确定的第一个变元就是z,因为z在指数位,对左边函数的值的影响是最大的。
怎么确定z的范围呢?题目已经给出了下界z>1,而z的上界要取决于x和y的取值。当x和y尽可能的小时,z才会变大。因为0<x<y,故取极限情况x=1,y=2,则方程变为了2^Z+2Z+1=K,故2^Z<K,Z<log(2)K,这就是Z的上界了。
因为Z的取值范围比较小,所以采取枚举Z的策略。而X和Y在方程中的地位是等价的,所以只能枚举一个,再二分另一个。
这里采用的是枚举x,枚举x也要有范围,所以要求x的上界。
取极限情况Y=X,,则方程变为2X^Z+X^2*Z=K,所以X^Z<K,故X的上界为K^1/Z
到此为止,我们只需要枚举Z和X,再二分判断是否存在Y满足条件,若存在,则找到了一组解。
对这个问题,还有一个很有效的剪枝,即把Z=2的情况单独列出来,这时候左边变成了一个完全平方式,这个式子的解的个数可以很快求出,不需要进行大量枚举。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int k;
ll ipow(ll a, ll b)
{
if(b == 0)
return 1;
ll x = ipow(a,b/2);
if(b%2) return x*x*a;
else return x*x;
}
void solve()
{
int cnt = 0;
//z=2时,(x+y)²=k
if(((int)sqrt(k))*((int)sqrt(k)) == k)
cnt += (sqrt(k)-1)/2;//x!=y
//z>=3时
ll maxz = (ll)(log(k)/log(2));
for(ll z = 3; z <= maxz; z++)
{
ll maxy = (ll)pow(k,1.0/z);
// printf("maxy=%lld\n",maxy);
for(ll x = 1; x < maxy; x++)
{
ll ly = x+1;//y > x
ll ry = maxy+1;
ll t = ipow(x,z);
while(ly < ry)
{
ll mid = (ly+ry)/2;
// printf("%lld %lld %lld %d\n", x,mid,z,k);
if(t + ipow(mid,z) + x*mid*z == k)
{
cnt++;
break;
}
else if(t + ipow(mid,z) + x*mid*z < k)
ly = mid+1;
else if(t + ipow(mid,z) + x*mid*z > k)
ry = mid;
}
}
}
printf("%d\n",cnt);
}
int main()
{
while(scanf("%d",&k) && k != 0)
solve();
return 0;
}