POJ 1186方程的解数(DFS+哈希)

题意

Time Limit: 15000MS Memory Limit: 128000K
Description
已知一个n元高次方程: k 1 x 1 p 1 + k 2 x 2 p 2 + . . . . . . + k n x n p n = 0 k_1x_1^{p1}+k_2x_2^{p_2}+......+k_nx_n^{p_n}=0 k1x1p1+k2x2p2+......+knxnpn=0
其中: x 1 , x 2 , . . . , x n 是 未 知 数 , k 1 , k 2 , k n 是 系 数 , p 1 , p 2 , . . . p n 是 指 数 x_1,x_2,...,x_n是未知数,k_1,k_2,k_n是系数,p_1,p_2,...p_n是指数 x1,x2,...,xnk1,k2,knp1,p2,...pn,且方程中的所有数均为整数。
设未知数 1 &lt; = x i &lt; = M , i = 1 , . . . , n 1&lt;=x_i&lt;=M,i=1,...,n 1<=xi<=M,i=1,...,n,求这个方程的整数解的个数。
1 &lt; = n &lt; = 6 ; 1 &lt; = M &lt; = 150 1&lt;=n&lt;=6;1&lt;=M&lt;=150 1<=n<=6;1<=M<=150
∣ k 1 M p 1 ∣ + ∣ k 2 M p 2 ∣ + . . . . . . + ∣ k n M p n ∣ &lt; 2 31 |k_1M^{p_1}|+|k_2M^{p_2}|+......+|k_nM^{p_n}|&lt;2^{31} k1Mp1+k2Mp2+......+knMpn<231
方程整数解的个数小于 2 31 2^{31} 231

Input
第1行包含一个整数n。第2行包含一个整数M。第3行到第n+2行,每行包含两个整数,分别表示 k i k_i ki p i p_i pi。两个整数之间用一个空格隔开。第3行的数据对应i=1,第n+2行的数据对应i=n。

Output
仅一行,包含一个整数,表示方程的整数解的个数。

Sample Input
3
150
1 2
-1 2
1 2

Sample Output
178

思路: 给定N和M,有N个系数 k 1 . . . k N k_1...k_N k1...kN,和N个指数 p 1 , . . . p N p_1,...p_N p1,...pN,有N个未知数 x 1 , x 2 , . . . , x N x_1,x_2,...,x_N x1,x2,...,xN,每个未知数可能的范围为[1, M],求满足题目给出方程的解的个数。

乍一看最简单的思路便是暴力枚举,对于每一个未知数 x i ( 1 &lt; i &lt; N ) x_i(1&lt;i&lt;N) xi1<i<N,尝试[1-M]里的所有整数值,带入方程,满足方程则解的数量加一,这种思路可以通过dfs实现,时间复杂度为 O ( M N ) O(M^N) O(MN),考虑最坏情况下的时间复杂度为 O ( 15 0 6 ) O(150^6) O(1506),显然直接枚举会超时。

对于这种指数级复杂度的算法,有一种非常有用的方法来降低时间复杂度——就是使用双向DFS,两边一起搜索,再中间判断状态是否符合 (meet-in-middle)

双向DFS将变量分为2部分,假设有N个未知数,第一个dfs枚举前1-N/2个未知数,对于每一种可能,计算出:
s u m 1 = k 1 x 1 p 1 + . . . + k N / 2 x N / 2 p N / 2 sum_1=k_1x_1^{p_1}+...+k_{N/2}x_{N/2}^{p_{N/2}} sum1=k1x1p1+...+kN/2xN/2pN/2
并且把计算结果存储在map中(map的key为sum1,value为前一半dfs sum1出现次数),这样之后使用sum1的结果时才够快。定义

map<int, int> dic;
dic[sum]++

后一个dfs枚举后N/2+1-N个未知数,对每一种状态计算出
s u m 2 = k N / 2 + 1 x N / 2 + 1 p N / 2 + 1 + . . . + k N x N p N sum_2=k_{N/2+1}x_{N/2+1}^{p_{N/2+1}}+...+k_{N}x_{N}^{p_{N}} sum2=kN/2+1xN/2+1pN/2+1+...+kNxNpN
此时如果有 − s u m 2 = = s u m 1 -sum_2 == sum_1 sum2==sum1 s u m 1 + s u m 2 = 0 sum_1+sum_2=0 sum1+sum2=0满足题目中的方程,将解的数目记录下来,dic里以及存储了sum1的结果,用C++可以写作:

if (dic.count(-sum2)) ans += dic[-sum2]

这样通过两个dfs,该题最坏情况下的复杂度下降到 O ( 15 0 3 ∗ 2 ) O(150^3*2) O(15032),不过需要使用map存储sum1的结果,实际上是用空间换时间。

code v1
通过上面的思路,我直接使用了stl里的map作为哈希表,但是map底层实现是用高度平衡的binary search trees.,查找一个元素的时间是 O ( l o g N ) O(logN) O(logN),N为map中的数据量。在这题用map提交TLE。C++11引入了unordered_map, unordered_map查找的时间复杂度为 O ( 1 ) O(1) O(1),可是POJ貌似不支持C++11,于是需要自己实现一个查找尽量在常数时间内的哈希表。

以下代码为使用STL中的map,提交超时,但是作为理解算法还是可以参考。

#include <iostream>
#include <map>
#include <cstring>
using namespace std;
int N, M;
int k[8], x[8], p[8], ans, mid;

long pow(long x, long y)
{
	long sum = x;
	for (long i = 1; i < y; i++)
	{
		sum *= x;
	}
	return sum;
}

void dfs_left(int steps, long sum, map<long, int>& dic)
{
	if (steps == mid+1)
	{
		dic[sum]++;
		return;
	}
	for (int i = 1; i <= M; i++)//x -> 1-M
	{
		x[steps] = i;
		dfs_left(steps + 1, sum + k[steps] * pow(x[steps], p[steps]), dic);
	}
}

void dfs_right(int steps, long sum, map<long, int>& dic)
{
	if (steps == N + 1)
	{
		if (dic.count(-sum))
			ans += dic[-sum];
		return;
	}
	for (int i = 1; i <= M; i++)
	{
		x[steps] = i;
		dfs_right(steps + 1, sum + k[steps] * pow(x[steps], p[steps]), dic);
	}
}


int main()
{

	while (cin >> N)
	{
		ans = 0;
		map<long, int> dic;
		cin >> M;
		for (int i = 1; i <= N; i++)
		{
			cin >> k[i] >> p[i];
		}
		mid = N / 2;
		dfs_left(1, 0L, dic);
		dfs_right(mid+1, 0L, dic);
		cout << ans << endl;
	}

	return 0;
}

code_v2
自己用数组实现一个哈希表

#include <iostream>
using namespace std;
const int NUM = 10000003;

int N, M;
long k[8], x[8], p[8], ans, mid;

typedef struct  
{
	int sum, value;
}hash_table;
hash_table hsh[NUM];

long get_index(long x)//哈希函数:输入x,给出x在hsh的下标
{
	long index = abs(x);
	if (index >= NUM)
		index = index % NUM;
	while (hsh[index].value != 0 && hsh[index].sum != x)//线性地址再探测
	{
		index += 1;
		if (index >= NUM)
			index = index % NUM;
	}
	return index;
}

void insert_num(long x)//将x插入哈希表
{
	long index = get_index(x);
	hsh[index].sum = x;
	hsh[index].value++;
}

long get_value(long x)
{
	long index = get_index(x);
	return hsh[index].value;
}

long pow(long x, long y)
{
	long sum = x;
	for (long i = 1; i < y; i++)
	{
		sum *= x;
	}
	return sum;
}

void dfs_left(int steps, long sum)
{
	if (steps == mid+1)//[0-mid]
	{
		insert_num(sum);
		return;
	}
	for (int i = 1; i <= M; i++)//x -> 1-M
	{
		x[steps] = i;
		dfs_left(steps + 1, sum + k[steps] * pow(x[steps], p[steps]));
	}
}

void dfs_right(int steps, long sum)
{
	if (steps == N + 1)//[mid+1, N]
	{
		int s = get_value(-sum);
		if (s)
			ans += s;
		return;
	}
	for (int i = 1; i <= M; i++)
	{
		x[steps] = i;
		dfs_right(steps + 1, sum + k[steps] * pow(x[steps], p[steps]));
	}
}
  

int main()
{
	ans = 0;
	cin >> N >> M;
	for (int i = 1; i <= N; i++)
	{
		cin >> k[i] >> p[i];
	}
	mid = N / 2;
	dfs_left(1, 0L);
	dfs_right(mid+1, 0L);
	cout << ans << endl;
	return 0;
}

提交
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值