如何递归地解决USACO 2013-周长-银

How to solve USACO 2013-Perimeter-Silver Recursively

本文关键字:USACO 解决 2013-周长 何递归 递归      更新时间:2023-10-16

所以我正在解决这个 USACO 2013 年 2 月银色竞赛 - 周长 - 问题 1。

问题链接:问题链接

链接到此问题的青铜版本:问题链接

链接到解决方案:银牌 - 链接到解决方案 铜牌 - 链接到解决方案

问题:

问题1:周长[布莱恩·迪恩,2013]

农民约翰在中间安排了N个干草包(1 <= N <= 50,000( 他的领域之一。 如果我们将字段视为 1,000,000 x 1,000,000 1 x 1 方形单元格的网格,每个干草包恰好占据其中一个 牢房(当然,没有两个干草包占据同一个牢房(。

FJ注意到他的干草捆都形成了一个大的相连区域,这意味着 从任何一捆开始,人们可以通过采取 北、南、东或西到直接相邻的一系列台阶 包。 然而,干草捆的连接区域可能包含"洞"—— 完全草包包围的空旷区域。

请帮助FJ确定由他的干草形成的区域的周长 包。 请注意,孔不会对周长产生影响。

问题名称: 周长

输入格式:

  • 第 1 行:干草包的数量,N。

  • 第 2..1+N 行:每行包含单个干草的 (x,y( 位置 bale,其中 x 和 y 都是范围内的整数 1..1,000,000. 位置 (1,1( 是 FJ 的左下角单元格 字段和位置 (1000000,1000000( 是右上角的单元格。

示例输入(文件 perimeter.in(:

8

10005 200003

10005 200004

10008 200004

10005 200005

10006 200003

10007 200003

10007 200004

10006 200005

输入详细信息:

由干草捆组成的连接区域如下所示:

二十

十 二十

三十

输出格式:

  • 1号线:干草捆连接区域的周长。

示例输出(文件外围(:

14

输出详细信息:

连接区域的周长为 14(例如, 该区域的左侧贡献了该总数的 3 长度(。 观察 中间的孔对这个数字没有贡献。

我做了什么

我继续对这个问题进行递归解决方案,如下所示:

#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i,a,b) for(auto (i)=a;i<b;i++)
#define list(i,N) for(auto (i)=0;i<N;i++)
typedef long long ll;
typedef vector<ll> vi;
typedef pair<ll,ll> pi;
#define mp make_pair
#define pb push_back
#define int ll
#define INF 1e18+5
#define mod 1000000007
//One map for storing whether a cell has hay bale or not
//And the other for visited - whether a cell has been visited or not
map<pi,bool> vis;
map<pi,bool> exists;
int ans = 0;
void solve(int i, int j){
//Check about the visited stuff
if(vis[mp(i,j)]) return;
vis[mp(i,j)] = true;
//Find the answer now
ans += 4;
if(exists[mp(i-1,j)]){
--ans; solve(i-1,j);    
}
if(exists[mp(i+1,j)]){
--ans; solve(i+1,j);
}
if(exists[mp(i,j+1)]){
--ans; solve(i,j+1);
}
if(exists[mp(i,j-1)]){
--ans; solve(i,j-1);
}
}
int32_t main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int N; cin >> N;
int first, second; //the starting point where we start the function...
while(N--){
int a,b; cin >> a >> b;
first = a; second = b; //in the end, it is just the coordinate specified in the last in the input...
exists[mp(a,b)] = true; //Hay Bale exists...
}
solve(first,second);
cout << ans << "n";
return 0;
}

基本上,我正在做的是:

  1. 从单元格开始。

  2. 首先,检查该单元格以前是否被访问过。如果是,请返回。如果没有,请访问它。

  3. 计数器上加 4 的所有四个边。

  4. 环顾细胞,了解其所有相邻的细胞。如果单元格也有干草包,请从计数器中减去 1(无需添加边界(,然后转到 2。

我面临的问题

请注意,此代码还计算孔内所需的边界。但我们不需要将其包含在我们的答案中。但是,我不知道如何从我们的答案中排除这一点......

为什么我提到青铜问题

如果你看到青铜问题的解决方案(这只是同一个问题,但有不同的约束(,Brian Dean先生也在这里实现了这种递归解决方案,类似于我在代码中所做的。代码如下:

#include <stdio.h>
#define MAX_N 100
int already_visited[MAX_N+2][MAX_N+2];
int occupied[MAX_N+2][MAX_N+2];
int perimeter;
int valid(int x, int y)
{
return x>=0 && x<=MAX_N+1 && y>=0 && y<=MAX_N+1;
}
void visit(int x, int y)
{
if (occupied[x][y]) { perimeter++; return; }
if (already_visited[x][y]) return;
already_visited[x][y] = 1;
if (valid(x-1,y)) visit(x-1,y);
if (valid(x+1,y)) visit(x+1,y);
if (valid(x,y-1)) visit(x,y-1);
if (valid(x,y+1)) visit(x,y+1);
}
int main(void)
{
int N, i, x, y;
freopen ("perimeter.in", "r", stdin);
freopen ("perimeter.out", "w", stdout);
scanf ("%d", &N);
for (i=0; i<N; i++) {
scanf ("%d %d", &x, &y);
occupied[x][y] = 1;
}
visit(0,0);
printf ("%dn", perimeter);
return 0;
}

为什么此解决方案不适用于白银

这是因为我正在解决的问题的白银版本中的约束具有更高的约束,但时间限制相同。这会使代码超时。

因此,如果有人能帮助我解决这个问题,以排除中间孔所占用的周长,我将不胜感激。

您的解决方案与发布的第二个解决方案非常相似。但是你不是在草捆上行走,而是在外围行走:

void solve(int i, int j){
if(vis[mp(i,j)]) return;
if(exists[mp(i,j)]) return;
if(there_is_no_bale_next_to(i,j)) return; // consider all 8 directions
vis[mp(i,j)] = true;
ans ++;
solve(i-1,j);    
solve(i+1,j);
solve(i,j+1);
solve(i,j-1);
}

您首先在绝对在周边的点(如最西端(上运行solve

您的解决方案的问题在于它以"X"点为目标,这也不可避免地会计算孔数。请考虑启动一个在物体周围移动而不实际进入物体的洪水填充。我下面的解决方案实现了这个想法,因此不计算漏洞。布莱恩·迪恩(Brian Dean(在官方社论中的解决方案也是基于这个想法,所以你也应该看看。

#include<bits/stdc++.h>
using namespace std;
int n, ans = 0;
map<pair<int,int>, bool> m, vis;
pair<int,int> p = {INT_MAX, INT_MAX};
bool adj (int i, int j) {
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
if (!x & !y) continue;
if (m[{i + x, j + y}]) return true;
}
}
return false;
}
int get_cnt (int i, int j) {
int res = 0;
if (m[{i, j + 1}]) res++;
if (m[{i, j - 1}]) res++;
if (m[{i + 1, j}]) res++;
if (m[{i - 1, j}]) res++;
return res;
}
void floodfill (int i, int j) {
if (m[{i, j}] || vis[{i, j}] || !adj(i, j)) return;
vis[{i, j}] = true;
ans += get_cnt(i, j);
floodfill (i, j + 1);
floodfill (i, j - 1);
floodfill (i + 1, j);
floodfill (i - 1, j);
}
int main () {
cin >> n;

for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
m[{x, y}] = true;
p = min(p, {x, y});
}
floodfill (p.first - 1, p.second);
cout << ans << endl;
}