扫描线
\(\text{luogu-5490}\)
求 \(n\) 个四边平行于坐标轴的矩形的面积并。
\(1 \le n \le {10}^5\),\(0 \le x_1 < x_2 \le {10}^9\),\(0 \le y_1 < y_2 \le {10}^9\)。
扫描线可以用于快速计算多个矩形的面积并。扫描线的运行过程和名称类似,即一根线沿一个方向扫描所有图形,如下图。
可以发现,在扫描时我们将一个不那么规则的区域,切割成了互不相交的若干个矩形。这若干个矩形的面积之和就是所求的答案。
如何计算切割出的单个矩形面积呢?我们需要知道矩形的底和高。矩形的高很好求,即为排序后相邻两横线间的距离。那么如何求矩形的高呢?
想要维护矩形的高需要从扫描线的运行方式入手。每次扫到一根横线,我们或将这根横线加入,或将这根横线删除。
可以将一根横坐标在 \(l\sim r\) 范围内的的横线拆成 \(l\sim l+1,l+2\sim l+3,\dots,r-1\sim r\) 共 \(r-l\) 根长度为 \(1\) 的横线。容易想到,可以对所有长度为 \(1\) 的横线都开一个桶来储存这根横线的数量。储存值大于 \(0\) 的桶的数量即为当前矩形的高。
这样,将横线加入,就是把这根横线范围内所有桶的储存值 \(+1\),将横线删除,就是把这根横线范围内所有桶的储存值 \(-1\)。
无论是加入横线还是删除横线,每次的操作都是对一片区域内的桶 \(+1\) 或 \(-1\),最终查询的是储存值大于 \(0\) 的桶的数量。发现这种区间操作可以利用线段树来快速维护。
建树的过程和一般的线段树是一样的,这里不过多讲述。
线段树的每个节点,都维护一段区间内的桶,所以每个节点需要记录的信息有:
l,r:维护的区间,即左端点的右端点。w:这个区间被完整覆盖几次。x:这个区间内被覆盖过的桶的数量,即这个区间内被覆盖的长度。
所以在向下递归节点时,如果当前节点被操作区间完全覆盖,则直接对 cnt 进行修改;否则,如果操作区间和左儿子维护区间相交,则对左儿子递归,如果操作区间和右儿子维护区间相交,则对右儿子递归。
这些处理完之后,需要更新这个节点区间内被覆盖的长度 len。
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
和一般的线段树不同,我们不需要下传标记。因为加入一根横线,在之后必定会删除一根完全相同的横线。删除时会和加入时递归到同一个节点,并直接对这个节点修改。
由于根节点维护的是整个区间,所以查询根节点上储存的 len 即可知道当前被覆盖的桶的数量。
横坐标的范围较大,但可能出现的横坐标值的数量很少,我们可以对横坐标进行离散化处理。即每个桶记录的是一段区间,而不一定是一个长度为 \(1\) 的横线。
由于每个矩形会产生两个横坐标值,所以离散化后横坐标区间为 \(0\sim 2n\)。所以要开 \(2n\) 个桶,即线段树有 \(2n\) 个叶子节点。计算得出线段树需要不超过 \(4n\) 个节点,储存线段树的数组开到 \(4n\) 就够了,但需要注意在更新叶子节点时不要去查找它的子节点。
和一般的线段树类似,在递归时线段树的每一层最多只有 \(3\) 个节点被递归到,而线段树的层数约为 \(\log n\),故单次操作时间复杂度为 \(O(\log n)\)。共有 \(n\) 个矩形,每个矩形有 \(2n\) 个横线,故需要在线段树上操作 \(2n\) 次。忽略常数,总时间复杂度为 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 200005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct tree { ll l, r, w, x; } t[MAXN << 2];
struct node { ll l, r, y, tg; };
vector<node> lin;
vector<ll> h;
ll n;
bool cmp(node pl, node pr) { return pl.y < pr.y; }
void build(ll p, ll l, ll r) {
t[p] = {l, r, 0, 0};
if(l == r) return;
ll mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
int main() {
n = read();
for(int i = 1; i <= n; i ++) {
ll xl = read(), yl = read(), xr = read(), yr = read();
h.push_back(xl), h.push_back(xr);
lin.push_back({xl, xr, yl, 1});
lin.push_back({xl, xr, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
build(1, 0, h.size() - 2); ll res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
update(1, pl, pr - 1, lin[i].tg);
res += t[1].x * (lin[i + 1].y - lin[i].y);
}
cout << res << "\n";
return 0;
}
\(\text{hdu-1542}\)
古希腊的几篇古籍里都提到了传说中的亚特兰蒂斯岛。有些书里甚至附带了岛上部分区域的地图。但遗憾的是,这些地图描绘的都是岛上不同的区域。你的朋友比尔想知道这些地图覆盖的总面积。你(不幸地)自告奋勇写个程序来算算这个总面积。
\(1 \le n \le 100\),\(0 \le x_1 < x_2 \le 10^5\),\(0 \le y_1 < y_2 \le 10^5\)。
和 luogu-5490 一摸一样,只不过有小数和多测。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 200005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct tree { ll l, r, w; double x; } t[MAXN << 2];
struct node { double l, r, y; ll tg; };
vector<node> lin;
vector<double> h;
ll n, cnt;
bool cmp(node pl, node pr) { return pl.y < pr.y; }
void build(ll p, ll l, ll r) {
t[p] = {l, r, 0, 0};
if(l == r) return;
ll mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
int main() {
while(cin >> n) {
if(!n) break; lin.clear(), h.clear();
for(int i = 1; i <= n; i ++) {
double xl, xr, yl, yr;
cin >> xl >> yl >> xr >> yr;
h.push_back(xl), h.push_back(xr);
lin.push_back({xl, xr, yl, 1});
lin.push_back({xl, xr, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
build(1, 0, h.size() - 2); double res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
update(1, pl, pr - 1, lin[i].tg);
res += t[1].x * (lin[i + 1].y - lin[i].y);
}
printf("Test case #%lld\nTotal explored area: %.2lf\n\n", (++ cnt), res);
}
return 0;
}
\(\text{hdu-1255}\)
给定平面上若干个矩形,求出被这些矩形覆盖至少 \(2\) 次的面积。
\(1 \le T \le 100\),\(1 \le n \le 1000\),\(0 \le x_1,x_2,y_1,y_2 \le 10^5\)。
只需要维护被覆盖至少一次的和至少两次的贡献。
upd 的过程需要改一下:
void upd(ll p, ll l, ll r) {
if(!t[p].w)
t[p].x1 = t[p << 1].x1 + t[p << 1 | 1].x1,
t[p].x2 = t[p << 1].x2 + t[p << 1 | 1].x2;
else if(t[p].w == 1)
t[p].x1 = h[r + 1] - h[l],
t[p].x2 = t[p << 1].x1 + t[p << 1 | 1].x1;
else t[p].x2 = t[p].x1 = h[r + 1] - h[l];
return;
}
其次这道题比较神秘,实际上是答案保留两位小数,向上取整,并非简单的保留两位小数。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
#define MAXN 200005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct tree { ll w; double x1, x2; } t[MAXN << 2];
struct node { double l, r, y; ll tg; };
vector<node> lin;
vector<double> h;
ll T, n;
bool cmp(node pl, node pr) { return pl.y < pr.y; }
void upd(ll p, ll l, ll r) {
if(!t[p].w)
t[p].x1 = t[p << 1].x1 + t[p << 1 | 1].x1,
t[p].x2 = t[p << 1].x2 + t[p << 1 | 1].x2;
else if(t[p].w == 1)
t[p].x1 = h[r + 1] - h[l],
t[p].x2 = t[p << 1].x1 + t[p << 1 | 1].x1;
else t[p].x2 = t[p].x1 = h[r + 1] - h[l];
return;
}
void update(ll p, ll L, ll R, ll l, ll r, ll x) {
if(l <= L && R <= r) { t[p].w += x, upd(p, L, R); return; }
ll mid = (L + R) >> 1;
if(l <= mid) update(p << 1, L, mid, l, r, x);
if(mid < r) update(p << 1 | 1, mid + 1, R, l, r, x);
upd(p, L, R); return;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T --) {
lin.clear(), h.clear(); cin >> n;
for(int i = 1; i <= n; i ++) {
double xl, xr, yl, yr;
cin >> xl >> yl >> xr >> yr;
h.push_back(xl), h.push_back(xr);
lin.push_back({xl, xr, yl, 1});
lin.push_back({xl, xr, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
memset(t, 0, sizeof t); double res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
update(1, 0, h.size() - 2, pl, pr - 1, lin[i].tg);
res += t[1].x2 * (lin[i + 1].y - lin[i].y);
}
ll t = res * 1000; t += 5, t /= 10;
printf("%.2lf\n", t / 100.0);
}
return 0;
}
\(\text{luogu-1856} / \text{hdu-1828}\)
给定平面上的若干个矩形,求这些矩形的周长并。
\(0 \le n < 5000\),\(-10^4 \le x_1,x_2,y_1,y_2 \le 10^4\)。
这个数据范围实际上暴力就能过,时间复杂度是 \(O(n \cdot \max (x_2 - x_1, y_2 - y_1))\)。
但如果在大点的话,就用扫描线求周长并。
水平的周长是好处理的,其实反转一下 \(x,y\) 轴,竖直的周长也能求。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 20005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { ll l, r, t, w; } a[MAXN], b[MAXN];
ll n, c1, c2, c[MAXN];
bool cmp(node x, node y) {
if(x.t != y.t) return x.t < y.t;
return x.w > y.w;
}
int main() {
while(cin >> n) {
c1 = c2 = 0; ll res = 0;
for(int i = 1; i <= n; i ++) {
ll xl, xr, yl, yr; cin >> xl >> yl >> xr >> yr;
xl += 10000, xr += 10000, yl += 10000, yr += 10000;
a[++ c1] = {xl, xr, yl, 1}, a[++ c1] = {xl, xr, yr, 0};
b[++ c2] = {yl, yr, xl, 1}, b[++ c2] = {yl, yr, xr, 0};
}
sort(a + 1, a + c1 + 1, cmp);
sort(b + 1, b + c2 + 1, cmp);
for(int i = 0; i <= 20000; i ++) c[i] = 0;
for(int i = 1; i <= c1; i ++) for(int j = a[i].l; j < a[i].r; j ++)
if(a[i].w) res += !c[j], c[j] ++;
else res += (c[j] == 1), c[j] --;
for(int i = 0; i <= 20000; i ++) c[i] = 0;
for(int i = 1; i <= c2; i ++) for(int j = b[i].l; j < b[i].r; j ++)
if(b[i].w) res += !c[j], c[j] ++;
else res += (c[j] == 1), c[j] --;
cout << res << "\n";
}
return 0;
}
\(\text{hdu-3265}\)
给定平面上的若干个矩形,每个矩形都被扣掉了面积不为 \(0\) 的一个小矩形,求这些矩形的面积并。
\(1 \le n \le 5 \times 10^4\),\(0 \le x_1 \le x_3 < x_4 \le x_2 \le 5 \times 10^4\),\(0 \le y_1 \le y_3 < y_4 \le y_2 \le 5 \times 10^4\)。
把一个被扣掉小矩形的大矩形划分成四个矩形插入,然后跑扫描线。
注意: 有可能 \(x_1 = x_3\),因此拆分成四个矩形之后可能会有若干个面积为 \(0\),注意防止越界!
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 800005
struct tree { ll l, r, w, x; } t[MAXN << 2];
struct node { ll l, r, y, tg; };
vector<node> lin;
vector<ll> h;
ll n;
bool cmp(node pl, node pr) {
if(pl.y != pr.y) return pl.y < pr.y;
return pl.tg > pr.tg;
}
void build(ll p, ll l, ll r) {
t[p] = {l, r, 0, 0};
if(l == r) return;
ll mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void upd(ll p) {
if(t[p].w > 0) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
ll mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) update(p << 1, l, r, x);
if(r > mid) update(p << 1 | 1, l, r, x);
upd(p); return;
}
int main() {
while(cin >> n) {
if(!n) break;
h.clear(), lin.clear();
for(int i = 1; i <= n; i ++) {
ll xl, yl, xr, yr, lx, ly, rx, ry;
cin >> xl >> yl >> xr >> yr >> lx >> ly >> rx >> ry;
h.push_back(xl), h.push_back(xr), h.push_back(lx), h.push_back(rx);
lin.push_back({xl, lx, yl, 1}), lin.push_back({xl, lx, yr, -1});
lin.push_back({rx, xr, yl, 1}), lin.push_back({rx, xr, yr, -1});
lin.push_back({lx, rx, yl, 1}), lin.push_back({lx, rx, ly, -1});
lin.push_back({lx, rx, ry, 1}), lin.push_back({lx, rx, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
build(1, 0, h.size() - 2); ll res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
if(pl <= pr - 1) update(1, pl, pr - 1, lin[i].tg); // 防越界!
res += t[1].x * (lin[i + 1].y - lin[i].y);
}
cout << res << "\n";
}
return 0;
}
\(\text{codeforces-1969e}\)
给定一个长为 \(n\) 的整数序列 \(a\)。我们称 \(a\) 的一个连续子序列是它的一个子段(即对于每一个满足 \(1 \leq l \lt r \leq n\) 的整数 \(l,r\),形如 \([a_l,a_{l+1},\cdots a_r]\) 的序列)。我们称一个子段是独特子段,当且仅当存在一个整数在这个子段中出现恰好一次。
你可以进行以下操作任意次(可能为零):选择一个序列中的元素并用任意整数替换它。
求最小的能使得序列 \(a\) 的每个子段都成为独特子段的操作次数。
\(1 \le T \le 10^4\),\(1 \le \sum n \le 3 \times 10^5\),\(1 \le a_i \le n\)。
以下部分题解来自于 题解:CF1969E Unique Array - 洛谷专栏。
首先,若以 \(l\) 为横轴 \(r\) 为纵轴建立平面直角坐标系,再对于所有的子区间 \([l,r](1\le l\le r\le n)\) ,将之对应与坐标系中左下角为 \((l-1,r-1)\) 右上角 \((l,r)\) 的小正方形,其分布应如下图。

接着,由题意,对于序列中的一个下标 \(i\) 及其对应的数 \(a_i\),设\(pre_i=\max(\{j|j<i\land a_i=a_j\}\cup\{0\})\) , \(nxt_i=\min(\{j|j>i\land a_i=a_j\}\cup\{n+1\})\) 。则可以确定对于所有 \(l\in [pre_i+1,i],r\in[i,nxt_i-1]\) 都有 \(a_i\) 在子区间 \([l,r]\) 中出现恰好一次,因而 \([l,r]\) 为独特子段。显然,这些独特子段的 \(l\) 和 \(r\) 所对应的小正方形构成一个矩形。
(如序列 \([3,1,2,1,3,2,1]\) ,对于 \(i=4,a_i=1,pre_i=2,nxt_i=7\) ,其贡献的独特子段所对应的矩形即为下图,表示 \([3,4],[3,5],[3,6],[4,4],[4,5],[4,6]\) 均为独特子段。)

显然,将每个 \(i\) 贡献的矩形覆盖至坐标系中,此时所有被覆盖过的小正方形所对应的区间一定是独特子段,而从未被覆盖过的一定不是(不存在 \(a_i\) 在这个子段中出现恰好一次)。
考虑扫描线求面积并的过程,设在 \(r\) 从小到大增加的过程中处理到第 \(i\) 行,由于无后效性,令 \(r\le i-1\) 的区域(即 \(r<i\) 的所有区间对应的小正方形)都已处理,若发现第 \(i\) 行(即坐标系中 \(i-1\le r\le i\) 的部分)存在未被覆盖的小正方形,即此时该行的面积并小于 \(i\) ,则出现区间右端点为 \(i\) 的非独特子段。
然后贪心处理,把 \(a_i\) 任意修改为未在序列中出现的数,此时,对于所有 \(l\in [1,i],r\in[i,n]\) 的区间 \([l,r]\) 都会变为独特子段,这相当于在坐标系中覆盖了一个顶点为 \((0,i-1),(i,i-1),(0,n),(0,n-1)\) 的矩形,那么只需要将扫描线中 \([0,i]\) 的区间覆盖次数加一即可。
关于贪心的正确性:
对于一个未被覆盖的小正方形或其对应的非独特子段区间 \([l,r]\) ,可以被多个 \(a_j(l\le j\le r)\) 的修改处理。但是,由于 \(r<i\) 的所有区间都已处理,对于任意 \(j<r\) ,修改 \(a_j\) 一定不会比修改 \(a_r\) 更优。
下图为序列 \([3,1,2,1,3,2,1]\) 的情况,蓝、绿、黄色矩形分别带表 \(a_i=1,2,3\) 时的贡献。图中修改 \(a_6\) 或任意 \(a_j(j<6)\) 均可覆盖区间 \([1,6],[1,7]\) 对应的小正方形,使区间 \([1,6],[1,7]\) 变为独特子段。然而对于 \(r\ge 6\) 的部分,修改 \(a_6\) 的所覆盖的矩形(图中长虚线框住的部分)严格包含 \(a_j\) 所覆盖的矩形(图中短虚线为修改 \(a_2\) 所覆盖的矩形,在 \(r\ge 6\) 时被长虚线完全覆盖),故而修改 \(a_6\) 一定不劣。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAXN 300005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct node { ll w, x; } t[MAXN << 2];
ll T, n, a[MAXN], l[MAXN], lst[MAXN];
void upd(ll p, ll L, ll R, ll k, ll l, ll r) {
if(L <= l && r <= R) t[p].w += k;
else {
ll mid = (l + r) >> 1;
if(L <= mid) upd(p << 1, L, R, k, l, mid);
if(R > mid) upd(p << 1 | 1, L, R, k, mid + 1, r);
}
if(t[p].w) t[p].x = r - l + 1;
else if(l == r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
int main() {
T = read();
while(T --) {
n = read(); ll res = 0;
for(int i = 1; i <= (n << 2); i ++) t[i].w = t[i].x = 0;
for(int i = 1; i <= n; i ++) l[i] = lst[i] = 0;
for(int i = 1; i <= n; i ++) {
a[i] = read(), l[i] = lst[a[i]] + 1;
upd(1, l[i], i, 1, 1, n);
if(lst[a[i]]) upd(1, l[lst[a[i]]], lst[a[i]], -1, 1, n);
if(t[1].x < i) res ++, upd(1, 1, i, 1, 1, n);
lst[a[i]] = i;
}
cout << res << "\n";
}
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19721997

浙公网安备 33010602011771号