首页 > 代码库 > 1393 - Highways 计数问题

1393 - Highways 计数问题

Hackerland is a happy democratic country with m×n cities, arranged in a rectangular m by ngrid and connected by m roads in the east-west direction and n roads in the north-south direction. By public demand, this orthogonal road system is to be supplemented by a system of highways in sucha way that there will be a direct connection between any pair of cities. Each highway is a straight line going through two or more cities. If two cities lie on the same highway, then they are directly connected.If two cities are in the same row or column, then they are already connected by the existing orthogonal road system (each east-west road connects all the m cities in that row and each north-south road connects all the n cities in that column), thus no new highway is needed to connect them. Your task is to count the number of highway that has to be built (a highway that goes through several cities on a straight line is counted as a single highway).

\epsfbox{p3720.eps}

Input 

The input contains several blocks of test cases. Each test case consists of a single line containing two integers 1$ \le$n , m$ \le$300 , specifying the number of cities. The input is terminated by a test case with n = m = 0 .

Output 

For each test case, output one line containing a single integer, the number of highways that must be built.

Sample Input 

2 4
3 3
0 0

Sample Output 

12
14

本题是思想挺难的题目,抽象思维要求挺高的。

使用动态规划法,首先要弄明白如何记录数据,并且明白记录的数据代表什么意义,否则是理解不了的。


网上解析这道题的本来就不多,也许太难理解了,而解析的博客,我都没看明白,不知其所以言,有些是解析太简单了,也许作者是那样理解的,但是表达出来别人又没法理解了;

这里也只是给出自己的理解,也许别人不一定能理解我为什么这样理解的。不过给出图,详细说明一下,希望我可以说清楚。

作者 靖心:http://blog.csdn.net/kenden23/article/details/29185889


这里需要使用双重动态规划法了,首先定义第一个表tbl

这个表的数值,例如tbl[i][j]代表,从第一个点[1][1]到[i][j]点组成的(i-1) * (j-1)个方格,从点[1][1]出发想象发出射线到边界上所有点的总数。

如下图是i = 2, j = 2的时候有3条射线


如下图:


这样计算好一个表,为什么要这样记录数据呢? 因为比如我们的长方形是由n*m格组成的,我们记录所有的方格组成的到边界的射线,就可以通过查表得到所有线段数了。

上图的3*3方格包含了2*2个方格的子长方形,可以通过查表tbl[2][2]得到这个子长方形的射线数。

记录这个数据是这句代码:

for (int i = 1; i < N; i++)
		{
			for (int j = 1; j < N; j++)
			{
				//tbl[i][j]代表从顶点1,1出发的射线到边界的数量
				tbl[i][j] = tbl[i-1][j] + tbl[i][j-1] - tbl[i-1][j-1]
				+ (GCD(i, j) ==  1? 1 : 0);
			}
		}
下标其实是记录方格数的,并非记录顶点数。
然后因为一个大的N*M个方格包含了所有的子方格,所以需要把这些子方格都包含进去,计算总数才算是结果。

for (int i = 1; i < N; i++)
		{
			for (int j = 1; j < N; j++)
			{
				//从小到大逆转使用的所谓inclusion-exclusion原理
				rs[i][j] = rs[i-1][j] + rs[i][j-1] - rs[i-1][j-1]
				+ tbl[i][j] - tbl[i>>1][j>>1];
			}
		}

最后,为什么需要-tbl[i>>1][j>>1]呢?因为大家可以按照上图画画射线,就知道tbl[i][j]的边界射线和tbl[i>>1][j>>1]射线重合了。

其实tbl是一个对角线对称的矩阵,可以只计算对角线上面的值*2优化。


要仔细观察,总结规律:

1 大的方格包含的子方格,可以通过填表防止重复计算,十分难抽象的一个思维

2 只需要记录边界射线就可以了,内射线和子方格的值一样

最后AC程序,挺快的,可以上UVa榜:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;

class Highways1393
{
	static const int N = 302;
	vector<vector<int> > tbl;
	vector<vector<int> > rs;

	int GCD(int a, int b)
	{
		return b==0? a : GCD(b, a % b);
	}

	void makeTbl()
	{
		for (int i = 1; i < N; i++)
		{
			for (int j = 1; j < N; j++)
			{
				//tbl[i][j]代表从顶点1,1出发的射线到边界的数量
				tbl[i][j] = tbl[i-1][j] + tbl[i][j-1] - tbl[i-1][j-1]
				+ (GCD(i, j) ==  1? 1 : 0);
			}
		}

		for (int i = 1; i < N; i++)
		{
			for (int j = 1; j < N; j++)
			{
				//从小到大逆转使用的所谓inclusion-exclusion原理
				rs[i][j] = rs[i-1][j] + rs[i][j-1] - rs[i-1][j-1]
				+ tbl[i][j] - tbl[i>>1][j>>1];
			}
		}
	}

public:
	Highways1393() : tbl(N, vector<int>(N)), rs(N, vector<int>(N))
	{
		makeTbl();

		int r, c;
		while (scanf("%d %d", &r, &c) && (r != 0 && c != 0))
		{
			printf("%d\n", rs[r-1][c-1]<<1);
		}
	}
};