POJ 1186 方程的解数

一道数据结构的水题。。。

题意为给予一个最多含六个未知数的方程k1*x1^p1+k2*x2^p2+k3*x3^p3+k4*x4^p4+k5*x5^p5+k6*x6^p6=0;求其整数解的个数。

其中x1,x2.....x6均大于等于1小于等于M(M为输入的一个正数,最大150最小1)。而且保证过程计算小于2^31。

到这里思路已经蛮清晰了,而且限时为15000MS。。。就是为了暴力过啊 = = (大力出奇迹?)

直接想到的就是暴力DFS,不过150^6也是不好搞的,所以加个优化,根据系数k的正负来分类项,比如选择负系数的项为库,将其所有的和的绝对值存起来,然后再累加正系数项的和,在库中进行查找。【就是相当于把式子变为k1*x1^p1+k2*x2^p2=k3*x3^p3+k4*x4^p4+k5*x5^p5+k6*x6^p6,其中k的值均为正整数,这样做的意义是为了降低时间复杂度,相当于左边为O(M^2),右边为O(M^4)。这样的话就想到加快查找的效率。。。用字典树存一边的和(二进制形式的)。

=.=  但是这样第一个问题是最坏情况下时间复杂度还是O(M^5),第二个问题是字典树空间要求太大,会超内存(这方法简直够蠢)

所以后来又重新考虑了下,直接选择线性表存,不过不是存一边的和,而是两边的和,而且两边项的数目要平均。

这里要先根据系数的大小进行一次排序,从小到大排。相当于是这样:

k1*x1^p1+k2*x2^p2+k3*x3^p3=k4*x4^p4+k5*x5^p5+k6*x6^p6,其中k的值不一定都为正整数,但一定有序

这样的话左边的项中一定含原来是负系数的项,在累加项的过程中,如果发现和小于0则不存入(一个小优化,因为项数是根据系数从小到大排的,所以如果左项的和已经是负数了,说明在左边已经含有原来是正系数的项,那么右边项肯定都是正数项,其和也是正数,无解了)。

然后对两个线性表排下序,时间复杂度O(C*log2C),C=M^3;时间复杂度过得去。然后同时遍历两个线性表,就可以求解了。

#include <stdio.h>
#include <set>
#include <algorithm>
using namespace std;

typedef long long ll;

int const Size = 4100000;
int const MAXM = 160;
int const MAXN = 31;

int Big;
int Ans;
int Zero;
int Num,M;
int Mat[MAXM][MAXN];

int Start[2],End[2];
int Neg[Size];
int Pos[Size];

struct Q
{
	int k,p;
}Data[MAXN];

bool cmp1(Q a,Q b)
{
	return a.k<b.k;
}

bool cmp2(Q a,Q b)
{
	return a.k>b.k;
}

void Pre()
{
	Big=0;
	Zero=1;
	Ans=0;
	Neg[0]=Pos[0]=0;
	int Limit=1<<30;
	ll now;
	for(int i=1;i<MAXN;i++)Mat[1][i]=1;
	for(int i=2,t;i<MAXM;i++)
	{
		now=i;t=1;
		while(now<Limit)
		{
			Mat[i][t++]=now;
			now*=i;
		}
	}
}

void Read_Case()
{
	scanf("%d %d",&Num,&M);
	for(int i=0;i<Num;i++)
	{
		scanf("%d %d",&Data[i].k,&Data[i].p);
		if(!Data[i].k)
		{
			i--;Num--;
			Zero*=M;
		}
	}
	sort(Data,Data+Num,cmp1);
}

void Get_List(int *List,int Left,int Right,int *S,int *E)
{
	int start=0,end=1;
	int Limit,K,P,ans;
	for(int i=Left;i<=Right;i++)
	{
		Limit=end;
		K=Data[i].k;P=Data[i].p;
		while(start<Limit)
		{
			for(int t=1;t<=M;t++)
			{
				ans=List[start]+K*Mat[t][P];
				if(ans>=0)List[end++]=ans;
			}
			start++;
		}
	}
	*S=start;*E=end;
	List[start-1]=-1;
}

void Search()
{
	int sum;
	int i=Start[0],t=Start[1];
	while(i<End[0])
	{
		if(Pos[i]!=Pos[i-1])
		{
			sum=0;
			while(t<End[1]&&Pos[i]>=Neg[t])
			{
				if(Neg[t]==Pos[i])sum++;
				t++;
			}
		}
		Ans+=sum;
		i++;
	}
}

void Solve()
{
	if(!Num)
	{
		printf("%d\n",Zero);
	}
	else if(Data[0].k>0||Data[Num-1].k<0)
	{
		puts("0");
	}
	else
	{
		int Mid=Num/2-1;

		for(int i=0;i<=Mid;i++)Data[i].k*=-1;
		Get_List(Pos,0,Mid,Start,End);

		sort(Data+Mid+1,Data+Num,cmp2);
		Get_List(Neg,Mid+1,Num-1,Start+1,End+1);

		sort(Pos+Start[0],Pos+End[0]);
		sort(Neg+Start[1],Neg+End[1]);

		Search();

		printf("%d\n",Ans*Zero);
	}
}

int main()
{
	Pre();
	Read_Case();
	Solve();
}

你妹啊又是快排!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值