[英]What's a good algorithm for Defense of a Kingdom?
我試圖解決王國問題的防御並提出了一個算法,但它超出了問題的時間限制。
我想知道一個很好的算法來在時限內解決這個問題。
問題:
西奧多實施了一個新的戰略游戲“王國保衛戰”。 在每個級別上,玩家都會保衛由矩形單元格網格表示的王國。 玩家在網格的一些單元格中建造弩塔。 塔保護同一行和同一列的所有單元格。 沒有兩座塔共用一行或一列。
位置的懲罰是最大的未設防矩形中的單元格數。 例如,圖片上顯示的位置有罰分 12。
幫助 Theodore 編寫一個程序來計算給定位置的懲罰。
輸入
輸入文件的第一行包含測試用例的數量。
每個測試用例由一行三個整數組成:w — 網格的寬度,h — 網格的高度和 n — 弩塔的數量(1 ≤ w,h ≤ 40 000;0 ≤ n ≤ min(w, H))。
以下 n 行中的每一行都包含兩個整數 xi 和 yi — 塔占據的單元格的坐標 (1 ≤ xi ≤ w; 1 ≤ yi ≤ h)。
輸出
對於每個測試用例,輸出一個整數——最大矩形中不受塔保護的單元格數。
例子
輸入:
1
15 8 3
3 8
11 2
8 6輸出:12
我會這樣做:
給定w
, h
作為比賽場地的寬度和高度,以及塔的坐標為(x1,y1) .. (xN,yN)
,將坐標分成兩個列表x1..xN
, y1..yN
,排序這兩個坐標列表。
然后計算空白空間,例如dx[] = { x1, x2-x1, ..., xN - xN-1, (w + 1) - xN }
。 對 y 坐標執行相同操作: dy[] = { y1, y2-y1, ..., yN - yN-1, (h + 1) - yN }
。 將max(dx)-1
乘以max(dy)-1
,您應該得到最大的未覆蓋矩形。 您必須將 delta 值減 1,因為更高坐標塔所覆蓋的線包含在其中,但沒有被覆蓋。
很容易看出,未設防單元集是關卡壁上未設防“孔”的笛卡爾積。 因此,一開始,您不需要將整個字段存儲在內存中——只存儲兩個塔坐標序列就足夠了。
第二個觀察結果是,在最終場中,所有塔都建立起來,最大的未設防矩形等於兩個最寬壁孔的笛卡爾積。 因此它的面積等於孔長度的乘積。 所以我們真正需要做的是找到兩個最寬的牆洞(一個在x
軸上,一個在y
軸上),並將它們的長度相乘。 那就是答案。
最后一點是關於輸入。 塔可能會以某種方式洗牌; 但是我們需要一種方法來導出所有孔的長度。 這可以很容易地通過首先對坐標序列進行排序,將一個和另一個分開,然后在一次通過中計算 {x i+1 -x i } 和 {y i+1 -y i } 來完成。 在同一遍中,我們甚至可以找到最大值——將它們相乘,你就完成了。
編輯我確實注意到這個程序中有一個小錯誤: - 當我最初提交它時,它只針對一個測試用例完成; 然后我回到我的 IDE 來改進算法以適用於多個測試用例。 讓它工作后,我回到這里編輯這篇文章,但我錯過了幾行關鍵的行。 它們現在已修復,如果想要保留每個測試用例的關聯記錄及其相應的懲罰值,我還添加了一些可以添加到此類的其他內容的注釋。 結束編輯
我把它封裝成一個類。 我很確定可能有一種更優雅的方法來做到這一點,但這是我想出的,使用您的文本文件示例來填充數據結構和返回懲罰的方法。
件.txt
1
15 8 3
3 8
11 2
8 6
DefenderBoard.h
#ifndef DEFENDER_BOARD_H
#define DEFENDER_BOARD_H
#include <fstream>
#include <vector>
#include <algorithm>
struct Coord {
unsigned x;
unsigned y;
Coord() : x(0), y(0) {}
Coord( unsigned xIn, unsigned yIn ) : x( xIn ), y( yIn ) {}
};
class DefenderBoard {
private:
std::string filename;
unsigned testcase;
unsigned gridsize_x;
unsigned gridsize_y;
unsigned numTowers;
std::vector<unsigned> penalties;
std::vector<Coord> cells;
public:
explicit DefenderBoard( const std::string& filenameIn );
~DefenderBoard();
void getPenalties( std::vector<unsigned>& penalties ) const;
private:
void getDataFromFile();
void calculatePenalty();
};
#endif // DEFENDER_BOARD_H
DefenderBoard.cpp
#include "DefenderBoard.h"
DefenderBoard::DefenderBoard( const std::string& filenameIn ) :
filename( filenameIn ),
gridsize_x( 0 ), gridsize_y( 0 ),
testcase( 0 ),
numTowers( 0 ),
penalty( 0 ) {
getDataFromFile();
}
DefenderBoard::~DefenderBoard() {
}
void DefenderBoard::getPenalties( std::vector<unsigned>& penaltiesOut ) const {
penaltiesOut = penalties;
}
void DefenderBoard::getDataFromFile() {
std::ifstream file;
file.open( filename );
if ( !file.is_open() ) {
// Note: This ExceptionHandler will not work for you.
// You will need to supply your own error or exception handling.
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed to read in file[" << filename << "]";
throw ExceptionHandler( strStream );
}
file >> testcase;
// Temps
Coord towerCoord;
// t -> testcase | c - tower coords
for ( unsigned t = 0; t < testcase; ++t ) {
file >> gridsize_x;
file >> gridsize_y;
file >> numTowers;
for ( unsigned c = 0; c < numTowers; ++c ) {
file >> towerCoord.x;
file >> towerCoord.y;
cells.push_back( towerCoord );
}
calculatePenalty();
// After Penalty is calculated this test case along with the penalty value
// can be stored into a struct containing both the penalty and a vector of cells
// which would be done here and then that struct would be stored into another container to this class
// If the above is choosen to be done then this needs to be called here instead of within the calculate function
// Clear our cells so it can be reused for each test case found in this file.
cells.clear();
}
file.close();
}
bool compareCoordsX( const struct Coord& a, const struct Coord& b ) {
return a.x > b.x;
}
bool compareCoordsY( const struct Coord& a, const struct Coord& b ) {
return a.y > b.y;
}
void DefenderBoard::calculatePenalty() {
std::vector<unsigned> xValDiff;
std::vector<unsigned> yValDiff;
unsigned diff = 0;
// First Sort By Largest X - Then Find The Differences
std::stable_sort( cells.begin(), cells.end(), compareCoordsX );
unsigned idx = 0;
for ( ; idx < cells.size(); ++idx ) {
// For First Iteration Only
if ( idx == 0 ) {
diff = gridsize_x - cells[idx].x;
xValDiff.push_back( diff );
} else {
diff = cells[idx-1].x - cells[idx].x - 1; // Don't Forget to Subract 1
xValDiff.push_back( diff );
}
}
// Also push back the last value - 1
xValDiff.push_back( cells.back().x - 1 );
// Do Same Thing For Y
std::stable_sort( cells.begin(), cells.end(), compareCoordsY );
idx = 0;
diff = 0;
for ( ; idx < cells.size(); ++idx ) {
// First Iteration Only
if ( idx == 0 ) {
diff = gridsize_y - cells[idx].y;
yValDiff.push_back( diff );
} else {
diff = cells[idx-1].y - cells[idx].y - 1; // Don't Forget to Subtract 1
yValDiff.push_back( diff );
}
}
// Also push back the last value - 1
yValDiff.push_back( cells.back().y - 1 );
unsigned largestX = xValDiff[0];
unsigned largestY = yValDiff[0];
idx = 0;
for ( ; idx < cells.size(); ++idx ) {
if ( xValDiff[idx] > largestX ) {
largestX = xValDiff[idx];
}
if ( yValDiff[idx] > largestY ) {
largestY = yValDiff[idx];
}
}
// Calculate Penalty And Store It
// EDIT - I did update this code after I had originally posted it
// and when I added the update I did forget to change this commented section
// penalty = largestX * largestY;
// It should be like this:
penalties.push_back( largestX * largestY );
// I did have this line of code here too but I moved it out of this function and into the getDataFromFile() method.
// cells.clear();
}
主程序
#include <iostream>
#include "DefenderBoard.h"
int main() {
std::vector<unsigned> penalties;
DefenderBoard board( "pieces.txt" );
board.getPenalties( penalties );
unsigned idx = 0;
for ( ; idx < penalties.size(); ++idx ) {
std::cout << penalties[idx] << " ";
}
std::cout << std::endl;
return 0;
}
輸出
12
第二次運行- 兩個測試用例:
件.txt
2
15 8 3
3 8
11 2
8 6
12 10 4
2 2
9 7
3 9
8 5
輸出
12 8
注意:沒有邊界檢查來查看文件中的單元坐標是否大於 MxN 板的大小。 因此,如果網格大小為 8x8 並且有一個坐標為 [12,2] 或 [5,9] 的部分,這些將破壞算法並給出無效結果。 我將把這些錯誤案例留作練習。
算法實現該算法的思想是先取大小,再減去最遠的那一塊。 然后你將取它並從中減去下一個最遠的一塊並減去 1 直到到達最后一塊,然后最后你將取最后一塊本身並從中減去 1。 這將為您提供一個方向的所有差異。 對另一個方向也重復此操作。 然后您正在尋找 x 和 y 中的最大尺寸。 擁有它們后,您需要做的就是計算 x 和 y 方向上最大差異的乘積,這將為您提供最大的開放區域。
我還注意到代碼中存在某種類型的重復,也可以通過向類添加幾個較小的函數來重構它,但是為了顯示算法,我認為在這種情況下並不真正需要它。 該算法唯一的雙重 for 循環是從文件中讀取數據。 實際計算基於坐標對的線性向量。 有 3 個單獨的 for 循環遍歷向量。 前兩個在 x 方向和 y 方向相同一次。 所以時間是基於輸入N的大小或長度。這應該是常數時間。 最后一個 for 循環仍然是 N,但查看大小為 N+1 的向量,它仍然是恆定時間,其中 N 是塔或坐標對的數量。 權衡是由於本地向量存儲 x 和 y 值的差異而存在一點內存開銷,對於小型和中等數據集,這不應該是一個問題。
免責聲明
在我的回答的評論中提到了這一點:
感謝您的努力,如果這會帶來負面影響,我很抱歉……但我想知道人們何時會在“類”中教授這種類型的“封裝”作為面向對象課程的一部分……這可能是我個人的品味,但是不帶參數並返回 void 的私有成員函數對我來說有點危險。 在閱讀代碼時,必須跟蹤所有可能被修改的成員就像在編程的黑暗時代那樣擁有全局變量一樣糟糕。 在這種特殊情況下,它們有助於隱藏第二個測試用例同時使用測試用例 1 和 2 中的塔的錯誤
是的,代碼中有一個錯誤,我已如上所述修復了該錯誤。 現在,如果查看此類對象的總體設計或 OO 方法,他們將看到該實現對此類對象的用戶或調用者是隱藏的。 他在這里發生的事情有一個特定的順序。
分步算法
附加功能- 這些可以添加 -
修正lisyarus 在評論中提出了一個很好的觀點,並基於此是非 OO 版本。
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
struct TowerCoordinate {
unsigned x;
unsigned y;
TowerCoordinate() : x(0), y(0) {}
TowerCoordinate( unsigned xIn, unsigned yIn ) : x( xIn ), y( yIn ) {}
};
struct GridSize {
unsigned width;
unsigned height;
GridSize() : width( 0 ), height( 0 ) {}
GridSize( unsigned widthIn, unsigned heightIn ) : width( widthIn ), height( heightIn ) {}
};
bool compareCoordsX( const struct TowerCoordinate& a, const struct TowerCoordinate& b ) {
return a.x > b.x;
}
bool compareCoordsY( const struct TowerCoordinate& a, const struct TowerCoordinate& b ) {
return a.y > b.y;
}
// Returns A Single Penalty
unsigned calculatePenalties( std::vector<TowerCoordinate>& towerLocations, GridSize& gridSize ) {
std::vector<unsigned> xValDiff, yValDiff;
unsigned diff = 0;
unsigned idx = 0;
// First Sort By Largest X - Then Find All Differences
std::stable_sort( towerLocations.begin(), towerLocations.end(), compareCoordsX );
for ( ; idx < towerLocations.size(); ++idx ) {
if ( idx == 0 ) {
diff = gridSize.width - towerLocations[idx].x;
xValDiff.push_back( diff );
} else {
diff = towerLocations[idx-1].x - towerLocations[idx].x - 1; // Don't Forget To Subtract 1
xValDiff.push_back( diff );
}
}
// Also Push Back (Last Value - 1)
xValDiff.push_back( towerLocations.back().x - 1 );
// Sort By Largest Y - Then Find All Differences
// First Sort By Largest X - Then Find All Differences
idx = 0;
diff = 0;
std::stable_sort( towerLocations.begin(), towerLocations.end(), compareCoordsY );
for ( ; idx < towerLocations.size(); ++idx ) {
if ( idx == 0 ) {
diff = gridSize.height - towerLocations[idx].y;
yValDiff.push_back( diff );
} else {
diff = towerLocations[idx-1].y - towerLocations[idx].y - 1; // Don't Forget To Subtract 1
yValDiff.push_back( diff );
}
}
// Also Push Back (Last Value - 1)
yValDiff.push_back( towerLocations.back().y - 1 );
unsigned largestX = xValDiff[0];
unsigned largestY = yValDiff[0];
idx = 0;
for ( ; idx < towerLocations.size(); ++idx ) {
if ( xValDiff[idx] > largestX ) {
largestX = xValDiff[idx];
}
if ( yValDiff[idx] > largestY ) {
largestY = yValDiff[idx];
}
}
return (largestX * largestY);
}
// Returns The Results Of All The Penalties For Each Case
std::vector<unsigned> getDefenderDataFromFile( const std::string& filename, unsigned& numTestCases, GridSize& gridSize, unsigned& numTowers, std::vector<TowerCoordinate>& towerLocations ) {
std::ifstream file;
file.open( filename );
if ( !file.is_open() ) {
// This ExceptionHandler will not work for you; you will need to supply your own error or exception handling.
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed to read in file[" << filename << "]";
throw ExceptionHandler( strStream );
}
file >> numTestCases;
TowerCoordinate towerCoord;
std::vector<unsigned> penalties;
for ( unsigned t = 0; t < numTestCases; ++t ) {
file >> gridSize.width;
file >> gridSize.height;
file >> numTowers;
for ( unsigned c = 0; c < numTowers; ++c ) {
file >> towerCoord.x;
file >> towerCoord.y;
towerLocations.push_back( towerCoord );
}
unsigned currentPenalty = calculatePenalties( towerLocations, gridSize );
penalties.push_back( currentPenalty );
towerLocations.clear();
}
file.close();
return penalties;
}
int main() {
unsigned numTestCases = 0;
unsigned numTowers = 0;
GridSize gridSize;
std::vector<TowerCoordinate> towerLocations;
std::vector<unsigned> penalties;
penalties = getDefenderDataFromFile( "pieces.txt", numTestCases, gridSize, numTowers, towerLocations );
unsigned idx = 0;
for ( ; idx < penalties.size(); ++idx ) {
std::cout << penalties[idx] << " ";
}
std::cout << std::endl;
return 0;
}
使用此算法的當前實現唯一需要注意的是,在每個測試用例之后都會清除塔位置的向量,因此一旦您獲得所有懲罰的最終結果,當前堆棧幀上的塔坐標容器將僅包含最后一組位置,並且不包含它們的任何先前迭代。 再一次沒有根據網格大小對塔坐標進行邊界檢查。
好吧,這可能是另一個第一個想法,
每個防守者至少有 1 個,最多 4 個相鄰的白色區域。
a:=0
a
在商店當前區域a
。如果你有一個 9x6 的網格。 你有3個塔。
首先計算具有 9 個元素的 x 軸的最小間隙。 我們有3個塔。 9/3 = 3。所以我們每 3 個元素放置一個塔。
[ ]
[ ]
[x]
[ ]
[ ]
[x]
[ ]
[ ]
[x]
這是一個 2 間隙最大值。 我們可以通過按塔數 (3) 划分剩余空間 (6) 來解決此問題。 6/3 = 2。
現在對於 y 軸也是如此。 6個方格。 3個塔。 6/3 = 每 2 格一塔:
[ ][x][ ][x][ ][x]
1 個空間最大間隙 (3/3)。
您現在擁有每個塔的 x 和 y 坐標(索引為 0):
1,2
3,5
5,8
最大的差距是 2x1 = 2。
[ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ]
[ ][x][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ]
[ ][ ][ ][x][ ][ ]
[ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][x]
我 99% 確信您可以為此創建一個通用公式,而無需返回每個城堡的 x,y 對和最大罰球區的循環。
它是象棋和車問題的一種變體。
想想 y 軸上坐標的最大相鄰間隙maxY_by_adjacent
。
想想 X 軸上坐標的最大相鄰間隙maxX_by_adjacent
。
為了找到相鄰的間隙,首先將它們排序。
這將使最長的未設防矩形width=maxX_by_adjacent & height=maxY_by_adjacent
。 現在,找出這個矩形maxX_by_adjacent * maxY_by_adjacent
上的單元格數。
int h,w,n;
scanf("%d %d %d",&w,&h,&n);
int x[n+2];
int y[n+2];
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x[i],&y[i]);
}
x[0] = 0;
x[n+1] = w + 1;
y[0] = 0;
y[n+1] = h + 1;
sort(x,x+n+1);
sort(y,y+n+1);
int maxX_by_adjacent=0;
int maxY_by_adjacent=0;
for(int i=1;i<=n+1;i++)
{
maxX_by_adjacent=max(maxX_by_adjacent,x[i]-x[i-1]-1);
}
for(int i=1;i<=n+1;i++)
{
maxY_by_adjacent=max(maxY_by_adjacent,y[i]-y[i-1]-1);
}
int ans=maxX_by_adjacent*maxY_by_adjacent;
printf("%d\n",ans);
你可以用貪心的方法來解決這個問題。我們的目標是找到最大的未設防矩形。矩形面積=行數*列數。我們必須最大化這兩個參數。要找到矩形中的最大列數,對牆壁的 x 坐標進行排序,並取兩個連續 x 坐標之間的差值。這種差值的最大值將是我們最終矩形中的最大列數。 做同樣的事情來找到最終矩形中的行數。參考我的代碼。 https://github.com/sahazeer123/SPOJ/blob/master/DEFKING.cpp
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.