【hihocoder 1249 Xiongnu's Land】线性扫描

时间:2021-09-18 15:42:04

2015区域赛北京赛区的三水,当时在赛场上没做出的原因是复杂度分析不正确导致把方法想复杂了。近来复习复杂度分析,觉得不能只是笼统地看渐进复杂度(big-O),更应根据算法的伪码计算真正的以基本操作数为变量的时间复杂度T(n)。

题意:在二维坐标系第一象限中,将一块顶点在原点边长为R的正方形土地用直线x=n一分为二,左侧分给Wei,右侧分给Huo。

土地中包含N个绿洲,每个绿洲是一个矩形,其位置和大小用四元组(L,T,W,H)表示,其中(L,T)为其左上方顶点的坐标,W,H为其宽度和高度。绿洲互不重叠。

求满足以下条件的一条划分直线(直线方程 x=n,0<=n<=R,n取整数):

(1)二人各自所得土地中绿洲面积应满足Wei>=Huo 且二者之差达到最小;

(2)在满足(1)的基础上,Wei的土地面积越大越好。

数据范围:1<=R<=1000000,    1<=N<=10000,     0<=L,T <=R,      1<=W,H<=R

复杂度分析:从所给数据范围看,R在106数量级,既然n取[0,R]的整数,那么若以R为数据规模,对x=i, i:0~R进行步长为1的线性扫描,假设每次迭代中基本操作次数为常数,则渐进复杂度为O(n)。假设计算环境1000ms的时间可完成108规模的基本运算,则本题O(n)的线性扫描思路从渐进复杂度的意义上讲是可行的。

确定了线性扫描的思路,接下来要考虑如何把每轮迭代代价控制在常数以及扫描停止的条件。

1. 如果在每轮迭代中,都检查所有N个绿洲以求出所划分的面积,那么每轮迭代的复杂度为T(N),整体复杂度上升到了T(N*R), 即1010显然不可行。

此方法的低效在于它没有为线性扫描这一“算法”设计合适的“数据结构”来存放绿洲的数据。题目输入的绿洲是一个个分散的个体,而从坐标出发的线性扫描需要快速获得以扫描位置 x=i 为自变量的左侧累加面积,这一“快速”,常数最好,至少不能和N在同一数量级;因此,要进行预处理将原始的绿洲数据转换为以横坐标为中心的统计值,以使每次迭代能用1~2个基本操作得到当前累加面积进而判断下一步的走向。

2. 扫描可以从最左侧的x=0开始,不断向右移动(保证绿洲面积左侧 < 右侧),遇到第一个理想位置(左侧>=右侧,满足了(1))后继续试探,直至抵达最理想的位置(左侧绿洲面积不增的条件下,为满足(2)尽量再往右移动)停止。由于扫描是线性的,可利用一个累加变量,每次只取当前“列”的面积作累加即可。

至此,对绿洲数据的预处理结果要求已经比较明确了,即得到 x=i 代表的一段宽度为1(可以是i ~ i+1)、高度为R的土地中绿洲的总面积,不妨用x[i]表示。

那么这段预处理所花费的时间呢,这回要以N为数据规模来考虑,假设所有绿洲被读入结构体数组中,则对j:0~N-1进行步长为1的线性扫描,假设每次迭代中基本操作次数为常数,N在104数量级,完全可行。但处理每个绿洲真的是常数时间吗,其实应该是T(W),因为要把宽度切分为长度为1的W段,累加到x数组的W个元素上。除非x用的不是朴素的一维数组,否则整体的渐进复杂度应为O(N*W),又是1010

按以上思路实现的代码,曾WA在long long类型,不应该~:

 #include <cstdio>
#include <cstring>
using namespace std;
const int MAX_R = ;
const int MAX_N = ; int K;
int R, N;
struct Rec{
int L, T;
long long W, H; //注意类型!!
}rec[MAX_N];
int x[MAX_R]; int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
scanf("%d",&K);
while(K--){
scanf("%d",&R);
scanf("%d",&N);
memset(x,,sizeof(x));
long long sum = ;
for(int i=; i<N; i++){
scanf("%d%d%d%d",&rec[i].L,&rec[i].T,&rec[i].W,&rec[i].H);
sum += rec[i].W*rec[i].H;
for(int j=rec[i].L; j<rec[i].L+rec[i].W; j++)
x[j] += rec[i].H;
}
long long wei = ;
int i;
for(i=; wei* < sum; i++)
wei += x[i];
while(x[i]== && i<R) i++;
printf("%d\n",i);
}
return ;
}

(若不可避免地1010的话,那么前面被否定的做法是否也可行呢,待续...)