思想:我们用所有矩形x轴值(所有矩形的左右边所在的x轴位置),把坐标轴划分成一个个小段。
我们暂且把一个矩形的下边界认为是“入边”,上边界认为是“出边”。
我们从左往右(外层循环以为x轴增长方向遍历左右边),从下往上(内层循环为以y轴增长方向遍历上下边)对边进行遍历。如遇到的是下边界则说明这个边的上面比下面多一个矩形覆盖,遇到上边界则说明这个边的上面比下面少一个矩形覆盖。我们把这条边上每个矩形覆盖数所对应的覆盖长度记下来,再乘以这条左右边到下条左右边的宽度,就能算出这段中每个矩形覆盖数所对应的面积。
如下图例子所示,绿色边为下边界,红色边为上边界,橙色数字为这段长度所对应的矩形覆盖数,蓝色虚线为当前左右边,深蓝色为下一条左右边,这里两条边的距离为 2 2 2 , g [ i ] g[i] g[i] 为 i i i 个矩形覆盖的面积之和。
算完所有
g
[
i
]
g[i]
g[i] 后我们就要算期望了。因为要算选
k
k
k 个矩形的面积期望,我们便要考虑矩形覆盖所带来的面积减少的影响。为了方便思考,不如我们以数格子的思想来想:选到这
i
i
i 个矩形覆盖其中一个的概率,然后再乘以它的面积,这就是它的期望。因为这个选到
i
i
i 个矩形覆盖的概率比较难求,我们不如算“不取这
i
i
i 个矩形的概率
C
n
−
i
k
C
n
k
\frac{C_{n - i}^{k}}{C_{n}^{k}}
CnkCn−ik",再
1
−
C
n
−
i
k
C
n
k
1 - \frac{C_{n - i}^{k}}{C_{n}^{k}}
1−CnkCn−ik,就是取到这个
g
[
i
]
g[i]
g[i] 所占有面积(格子)的概率。然后把所有情况连加起来,就有公式
a
n
s
=
∑
i
=
1
n
(
(
1
−
C
n
−
i
k
C
n
k
)
∗
g
[
i
]
)
ans = \sum_{i = 1}^{n}((1 - \frac{C_{n - i}^{k}}{C_{n}^{k}}) * g[i])
ans=i=1∑n((1−CnkCn−ik)∗g[i])
顺序输出每个 1 − n 1-n 1−n 的所算出的 a n s ans ans 即可。
const int N = 2e3+5;
const int MOD = 998244353;
int c[N][N]; //组合数
void init(){ //初始化
c[0][0] = c[1][0] = c[1][1] = 1;
for(int i = 2;i < N;++i){
c[i][0] = 1;
for(int j = 1;j <= i;++j){
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % MOD;
}
}
}
int qpow(int x,int n){
int ans = 1;
while(n){
if(n & 1)ans = (ans * x) % MOD;
x = (x * x) % MOD;
n >>= 1;
}
return ans % MOD;
}
struct lx{
int y,x1,x2,flag;
bool operator<(const struct lx & that)const{
return y < that.y;
}
};
struct ly{
int x,y1,y2,flag;
bool operator<(const struct ly & that)const{
return x < that.x;
}
};
void solve(){
int n;cin >> n;
vector<struct lx>linex; //存于x轴平行的边,也就是左右边
vector<struct ly>liney; //存于y轴平行的边,也就是上下边
init();
int x1,y1,x2,y2;
for(int i = 0;i < n;++i){
cin >> x1 >> y1 >> x2 >> y2;
linex.push_back({y1,x1,x2,1}); //左边为入边 标记为1
linex.push_back({y2,x1,x2,0}); //右边为出边 标记为0
liney.push_back({x1,y1,y2});
liney.push_back({x2,y1,y2});
}
sort(linex.begin(),linex.end());
sort(liney.begin(),liney.end());
vector<int>g(n + 1);
vector<int>tmp(n + 1);
int lowx = 0; //用于记录上一条边的x轴位置 算宽用
bool mark1 = 1;
for(auto [x,y1,y2] : liney){ //从左往右遍历平行于y轴的边
if(mark1 == 0){ //特判 刚遍历第一条边时不该进行算面积操作
for(int i = 1;i <= n;++i){
g[i] += tmp[i] * (x - lowx) % MOD;
g[i] %= MOD;
}
}
tmp = vector<int>(n + 1); //用于暂存 被i个矩形覆盖的边 的长度
lowx = x;
mark1 = 0;
bool mark2 = 1;
int lowy = 0;
int cnt = 0; //当前时被几(cnt)个矩形覆盖
for(auto [y,x1,x2,flag2]:linex){
if(x1 <= x && x < x2){
if(flag2) //flag2 == 1 为入边 cnt++ 否则为出边 cnt--
if(mark2){ //特判 刚遍历第一条边时cnt刚从0加到1,0无意义所以不存
mark2 = 0;
lowy = y;
cnt++;
}
else{
tmp[cnt] += y - lowy;
tmp[cnt] %= MOD;
cnt++;
lowy = y;
}
}
else{
tmp[cnt] += y - lowy;
tmp[cnt] %= MOD;
cnt--;
lowy = y;
}
}
}
}
for(int k = 1;k <= n;++k){
int ans = 0;
int p = qpow(c[n][k],MOD - 2); //预处理 逆元求C_{n}^{k}
for(int i = 1;i <= n;++i){
ans += ( (1 - (c[n - i][k]) * p % MOD + MOD) % MOD) * g[i]; //套公式
ans %= MOD;
}
cout << ans % MOD << endl;
}
}