描述
有一种大青蛙会跳越稻田,从而踩踏稻子。农民在早上看到被踩踏的稻子,希望知道晚上有多少青蛙穿越自家的稻田。每只青蛙总是沿着一条直线跳跃稻田,而且每次跳跃的距离都相同。
如图1和图2所示,稻田里的稻子组成一个栅格,每棵稻子位于一个格点上。而青蛙总是从稻田的一侧跳进稻田,然后沿着某条直线穿越稻田,从另一侧跳出去。
如图3所示,可能会有多只青蛙从稻田穿越。青蛙的每一跳都恰好踩在一棵水稻上,将这棵水稻踩坏。有些水稻可能被多只青蛙踩踏。当然,农民所见到的是图4中的情形,并看不到图3中的直线,也见不到别人家田里被踩踏的水稻。需要注意的是,农民可以根据水稻踩坏的程度来精确计算出一共几只青蛙踩踏过这一棵水稻,所以聪明的农民就能得到图4的水稻毁坏程度图。
我们可以认为每条青蛙跳跃路径上至少包括3棵被踩踏的水稻。而在一条青蛙跳跃路径的直线上,也可能会有些被踩踏的水稻不属于该行走路径,例如青蛙跳跃路径(2,3)(4,5)(6,7)经过(3,4),而(3,4)不属于跳跃路径。
注意:同一只青蛙不会第二次跳入同一片稻田,可能存在不同的两只青蛙的跳跃路径完全一样。
现在给定水稻破坏程度图,请你写一个程序,确定:最少有多少只青蛙穿越了稻田。例如,图4的答案是4,图3所示的跳跃路径数量就是答案。输入数据保证有解且最多14只青蛙穿越稻田。
输入
从标准输入设备上读入数据。第一行为测试数据数目。每组测试数据第一行上两个整数R、C,分别表示稻田中水稻的行数和列数,1≤R、C≤50。第二行是一个整数N,表示被踩踏的水稻数量, 3≤N≤700。在剩下的N行中,每行有三个整数,分别是一颗被踩踏水稻的行号(1~R)、列号(1~C)和被踩踏次数,三个整数之间用空格隔开。每棵被踩踏水稻只被列出
输出
对于每一组测试数据从标准输出设备上输出一个整数,表示最少有几只青蛙穿越了稻田。
样例输入
1
6 7
14
2 1 1
2 2 1
2 3 2
2 4 1
2 5 1
2 6 2
2 7 1
3 4 1
4 2 1
4 5 1
6 1 1
6 3 1
6 5 1
6 7 2
样例输出
4
分析
最开始看的题就是恼人的青蛙,最后一道题没想到会又与这个话题有关。枚举前两个点,之后再递归看能否让所有的点归于有效路径里面,按原来的思路改写后TLE,之后又加了些剪枝方式,示例降到1ms,但OJ上测试还是TLE,尝试多次无果,先放着吧,有空再想想,如果有了解怎么有效剪枝麻烦回复下。
实现
#include <iostream>
#include <algorithm>
#include <vector>
#include <ctime>
#include <map>
#define KEY(plant) ((plant.x << 6) + plant.y)
using namespace std;
int r, c, n, totalPlantCount, minPathCount;
struct PLANT
{
int x, y, num;
};
PLANT plants[701];
PLANT plant;
map<int, int> pointMap;
vector<int> searchPath(int i, int j, PLANT secPlant, int dX, int dY);
void findNextPathStartPlant(int startX, int plantCount, int pathCount) {
//如果所有践踏点都已覆盖掉的话则更新最小路径数,即最小青蛙数。
if (plantCount == 0) {
minPathCount = minPathCount > pathCount ? pathCount : minPathCount;
return;
}
if (pathCount >= minPathCount) {
return;
}
int dX, dY, pX, pY, findFirstJ;
for (int i = startX; i < n - 2; i++) {
if (plants[i].num <= 0) continue;//plants[i]是第一个点,必须保证未用完踩踏点。
findFirstJ = 0;
for (int j = i + 1; j < n - 1; j++) {
if (plants[j].num <= 0) continue;//plants[j]是第二个点,必须保证未用完踩踏点。
if (plants[i].num <= 0) break;
if (!findFirstJ) {
findFirstJ = j;
}
dX = plants[j].x - plants[i].x;
dY = plants[j].y - plants[i].y;
pX = plants[i].x - dX;
pY = plants[i].y - dY;
//第一点的前一点在稻田里, 说明本次选的第二点导致的x方向步长不合理(太小)
//取下一个点作为第二点
if (pX <= r && pX >= 1 && pY <= c && pY >= 1) continue;
//x方向过早越界了. 说明本次选的第二点不成立
//如果换下一个点作为第二点, x方向步长只会更大, 更不成立, 所以应该
//认为本次选的第一点必然是不成立的, 那么取下一个点作为第一点再试
pX = plants[j].x + dX;
if (pX > r || pX < 1) break;
pY = plants[j].y + dY;
if (pY > c || pY < 1) continue; //y方向过早越界了, 应换一个点作为第二点再试
vector<int> plantPos = searchPath(i, j, plants[j], dX, dY);
if (plantPos.size() > 0) {
plantCount -= plantPos.size();
//是一条路径则将所有踩踏点的可踩踏数减1。
for (std::vector<int>::const_iterator iter = plantPos.cbegin(); iter != plantPos.cend(); ++iter) {
// PLANT currPlant = plants[(*iter)];
// cout << "(" << currPlant.x << ", " << currPlant.y << ", " << currPlant.num << ") ";
plants[(*iter)].num--;
}
findNextPathStartPlant(i + 1, plantCount, pathCount + 1);
//恢复
plantCount += plantPos.size();
for (std::vector<int>::const_iterator iter = plantPos.cbegin(); iter != plantPos.cend(); ++iter) {
plants[(*iter)].num++;
}
}
}
if (plants[i].num > 0) {
//当前点没消耗完说明是孤立点,永远不可能踩到。
return;
}
if (findFirstJ) { i = findFirstJ; } //直到跳到下一个可用的点。
}
}
int main() {
// freopen("in.txt", "r", stdin);
// const clock_t begin_time = std::clock();
int t;
scanf("%d", &t);
while (t--) {
totalPlantCount = 0;
minPathCount = 15;
pointMap.clear();
scanf("%d %d", &r, &c);
//行数和列数, x方向是上下, y方向是左右
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d %d %d", &plants[i].x, &plants[i].y, &plants[i].num);
pointMap[KEY(plants[i])] = i;
totalPlantCount += plants[i].num;
}
//将水稻按x坐标从小到大排序, x坐标相同按y从小到大排序
sort(plants, plants + n);
findNextPathStartPlant(0, totalPlantCount, 0);
printf("%d\n", minPathCount);
}
// std::cout << float(clock() - begin_time) << "ms" << endl;
}
bool operator <(const PLANT& p1, const PLANT& p2) {
if (p1.x == p2.x) return p1.y < p2.y;
return p1.x < p2.x;
}
bool operator ==(const PLANT& p1, const PLANT& p2) {
return p1.x == p2.x && p1.y == p2.y;
}
int binarySearch(PLANT* a, int start, int end, PLANT key) {
int l = start, r = end - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (a[mid] == key) return mid;
else if (a[mid] < key) l = mid + 1;
else r = mid - 1;
}
return -1;
}
//判断从 secPlant点开始, 步长为dx, dy, 那么最多能走几步
vector<int> searchPath(int i, int j, PLANT secPlant, int dX, int dY) {
vector<int> plantPos;
plantPos.push_back(i);
plantPos.push_back(j);
PLANT plant;
plant.x = secPlant.x + dX;
plant.y = secPlant.y + dY;
// int pos = j, end;
while (plant.x <= r && plant.x >= 1 && plant.y <= c && plant.y >= 1) {
// end = pos + r * dX + dY + 1;
// if (end >= n) {
// end = n;
// }
// pos = binarySearch(plants, pos, end, plant);
int key = KEY(plant);
if (pointMap.count(key) > 0 && plants[pointMap[key]].num > 0) {
plantPos.push_back(pointMap[key]);
plant.x += dX;
plant.y += dY;
}
else {
//not found, 每一步都必须踩倒水稻才算合理, 否则这就不是一条行走路径
plantPos.clear();
break;
}
}
return plantPos;
}