题目链接
IPSC 2012 Problem H - Harvesting Potatoes
题目大意
有一块大小 (r imes c) 的网格状土豆田,其中每一个格子中都有一株土豆。 现在农夫将要收获这些土豆,农夫的收获过程是一轮轮进行的,每一轮中,农夫驾驶一辆土豆收割车经过一整行或者一整列,收获其中还未收获的最多 (d) 株土豆。在连续的一段收割过程中,农夫需要先打开机械铲,然后进行连续的收割,再关闭它。这一轮收获的难度是农夫调整机械铲的次数,也就是 (2 imes) 连续收割的段数。 农夫首先希望最小化收割的轮数,然后最小化所有轮中难度的最大值。现在他希望你能给出一组收割的方案。
(1leq r,cleq 400)
思路
这个题面容易让人误以为是 (DP) 题,但是经过若干次尝试之后,会发现大部分情况下,直接连续 (d) 个一段填就是最优的,如 (r=c=7,d=4) 时:
1 1 1 1 B B B
2 2 2 2 C C C
3 3 3 3 D D D
4 5 6 7 8 9 A
4 5 6 7 8 9 A
4 5 6 7 8 9 A
4 5 6 7 8 9 A
具体来说,先用 (1 imes d) 把网格的外圈填满,直至剩下一个 (r\%d imes c\%d) 的网格,此时选择较短的边作为左右边,然后一行行去填就行了。这是当难度最大值为 (2) 的时候最优的方案(之一)。
证明:
类似于 (k-domino) 的思路,对网格图进行染色,((i,j)) 被染上颜色 ((i+j)\%d),可以发现每个完整放在网格里的 (1 imes d) 都会把 (0) 到 (d-1) 的每种颜色覆盖一次,而两个颜色相同的格子必然不会被同一个 (1 imes d) 覆盖,所以覆盖网格所需的轮数 (geq) 格子最多的那种颜色的格子数量,可以发现即 (clfloor frac{r}{d} floor+dlfloorfrac{c}{d} floor+max(r\%d,c\%d)),而前面的方案刚好达到了这个下界。
但是对于 (r=c=6,d=4) 的情况,贪心就不正确了,它的方案应为:
1 1 1 1 8 9
2 2 6 7 2 2
4 5 3 3 3 3
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
而贪心需要 (10) 轮,以这个为启发,在 (r,c) 足够大时,我们可以得到一个至多需要 (lceilfrac{cr}{d} ceil) 轮的方案。
当 (r,cgeq d) 时,不妨设 (cleq r),我们将 (r) 行中为 (d) 的倍数的部分直接一列列填满,现在剩下了一个 (r\%d imes c) 的网格,对于这个网格,我们贪心一格格填上颜色,颜色使用超过 (d) 次后就换下一个颜色,由于 (cgeq d),所以同一列上不会有相同的颜色,于是考虑横着把颜色给涂上,这时需要给这些颜色段调整位置,如:
1 1 1 1 2 2
2 2 3 3 3 3
调整成
1 1 1 1
2 2 2 2
3 3 3 3
这样就可以在一轮中把相同的颜色一起涂上了,在最差的情况下,网格涂了 (c) 种颜色,且需要每行只能放的下一种颜色,即把 (r\%d) 行松成了 (c) 行,由于 (rgeq c),所以放的下,剩下空出来的位置给之前的列涂即可。由于之前要涂过列,所以这种构造的存在当且仅当 (r,cgeq d)。
仔细思考一下,由于行之间的顺序无关,我们可以通过合适的排序使得方案中的难度不超过 (4),就把这些颜色段按照第一个出现填此类颜色的位置从小到大排序即可:
1 1 1 1 > 1 1 1 1
2 2 2 2 > 3 3 3 3
3 3 3 3 > 5 5 5 5
4 4 4 4 > 2 2 2 2
5 5 5 5 > 4 4 4 4
6 > 6
然后就做完了,不过有时贪心地构造所需轮数也是 (lceilfrac{rc}{d} ceil),此时贪心解更优,所以两种构造都要做一下,比较一下轮数即可。
时间复杂度 (O(rc))
Code
// https://ipsc.ksp.sk/2012/real/problems/h.html
#include<iostream>
#include<fstream>
#include<cstring>
#include<vector>
#include<algorithm>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 404
#define PII pair<int, int>
#define fr first
#define sc second
using namespace std;
int a[N][N], n, m, d;
void flip(bool tf){
if(tf){
rep(i,1,max(n,m)) rep(j,1,i) swap(a[i][j], a[j][i]);
swap(n, m);
}
}
int main(){
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
int T; cin>>T;
while(T--){
cin>>n>>m>>d;
bool flag = false;
mem(a, 0);
int tm = m, cnt = 1;
while(tm >= d){
rep(i,1,n){ per(j,tm,tm-d+1) a[i][j] = cnt; cnt++; }
tm -= d;
}
int tn = n;
while(tn >= d){
rep(j,1,tm){ per(i,tn,tn-d+1) a[i][j] = cnt; cnt++; }
tn -= d;
}
int num = 0;
if(tn > tm) rep(j,1,tm){
rep(i,1,tn) a[i][j] = cnt, ++num == d ? (num = 0, cnt++) : 0;
if(num) num = 0, cnt++;
} else rep(i,1,tn){
rep(j,1,tm) a[i][j] = cnt, ++num == d ? (num = 0, cnt++) : 0;
if(num) num = 0, cnt++;
}
if(num == 0) cnt--;
if(n >= d && m >= d && cnt > (n*m+d-1)/d){
mem(a, 0), cnt = 1;
if(n < m) swap(n, m), flag = true;
int tot = (n%d)*m;
int j = 1, lft = tot;
vector<PII> pos;
rep(i,1, tot/d + int(tot%d > 0)){
pos.push_back({j, min(lft, d)});
rep(k,1,min(lft, d)) j = j%m+1;
lft -= d;
}
sort(pos.begin(), pos.end());
lft = tot;
rep(i,0,(int)pos.size()-1){
int j = pos[i].fr;
rep(k,1,pos[i].sc) a[i+1][j] = cnt, j = j%m+1;
lft -= d, cnt++;
}
rep(j,1,m){
int num = 0;
rep(i,1,n) if(!a[i][j])
a[i][j] = cnt, ++num == d ? (cnt++, num = 0) : 0;
}
}
flip(flag);
rep(i,1,n) rep(j,1,m) cout<<a[i][j]<<"
"[j == m];
cout<<endl;
}
return 0;
}