#include #include #include #include using namespace std; //快读 int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); } return x * f; } const int N = 500010; const int M = 50010; int n; //点的数量 vector a[M]; //记录每个点的坐标 //主席树的结构体 struct Node { int cnt; int l, r; } tr[N << 5]; //主席树,一般开32倍空间 int idx; //主席树的节点生成器 int root[M]; //版本与根节点的对应关系 int ver[M]; //记录y坐标是在哪个版本加入进去的,这是一个桶,性能用静态数组模拟应该是最强的,不要使用什么unordered_map之流 //标准的主席树insert int insert(int p, int l, int r, int x) { int q = ++idx; tr[q] = tr[p]; tr[q].cnt++; if (l == r) return q; int mid = (l + r) >> 1; if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x); else tr[q].r = insert(tr[p].r, mid + 1, r, x); return q; } //功能:主席树,在两个版本间 查询 某个区间中数字的增加个数 int query(int q, int p, int ql, int qr, int l, int r) { if (ql <= l && qr >= r) return tr[p].cnt - tr[q].cnt; //完整区间包含,直接返回两个版本的数字个数差,表示两个版本间增加的数字数量 int ans = 0; int mid = (l + r) >> 1; if (ql <= mid) ans = query(tr[q].l, tr[p].l, ql, qr, l, mid); //递归查询左半区间 if (qr > mid) ans += query(tr[q].r, tr[p].r, ql, qr, mid + 1, r); //递归查询右半区间 return ans; } bool solve() { //对于主席树的0号版本进行清空 root[0] = idx = tr[0].l = tr[0].r = tr[0].cnt = 0; //索引号生成器idx修改为0,主席树中0号结点左儿子,右儿子都不存在,0号版本中数字个数为0, 0号版本的根节点是0 memset(ver, 0, sizeof(ver)); //清空ver数组 for (int i = 1; i <= 50000; i++) { //从小到大枚举每一个x坐标 for (int j = 0; j < a[i].size(); j++) { //从小到大枚举每一个y坐标 //如果是第一个y坐标,那么l=0,这个0是哨兵的意思。如果不是第一个y坐标,那么l=当前y坐标的前一个y坐标,prev_y, int l = j == 0 ? 0 : a[i][j - 1]; //如果是最后一个y坐标,那么r=50001,否则r=当前y坐标的下一个y坐标,next_y int r = j == a[i].size() - 1 ? 50001 : a[i][j + 1]; // root[i]:当前版本的根 // root[x[a[i][j]]]:前一版本的根 // query的返回值是个整数,表示两个版本间两个区间内增加的数字个数,如果个数大于0,就return false if (query(root[i - 1], root[ver[a[i][j]]], l + 1, r - 1, 1, 50000) > 0) return false; } root[i] = root[i - 1]; for (int j = 0; j < a[i].size(); j++) { //从小到大枚举每一个y坐标 root[i] = insert(root[i], 1, 50000, a[i][j]); ver[a[i][j]] = i; //记录a[i][j]这个y坐标,是第i个版本中增加进去的 } } //如果上面所有的坐标点都讨论过,全都没有返回false,那就只能返回true return true; } /* NO YES */ int main() { //文件输入输出 #ifndef ONLINE_JUDGE freopen("HDU5820.in", "r", stdin); #endif while (n = read(), n) { //在使用快读时,要注意使用 ,n 而不是使用 && n!!!! read没有返回值!!!! // a数组装的是坐标(x,y),只不过它是以x坐标为第一维,第二维的y1,y2,y3,... 通过push_back加入到一维的x中 for (int i = 0; i < M; i++) a[i].clear(); //二维vector的清空操作,不用循环还真不行 int x, y; while (n--) { x = read(), y = read(); // 每个点的坐标 a[x].push_back(y); // 记录每个点的坐标,记录的方法很有意思,是一个vector,一维是x坐标,二维是y坐标 } //枚举每个可能的x坐标,对x坐标相同的点,按y坐标小由到大进行排序,这样,就是一个二维有序的数组啦,和结构体两个属性进行排序效果和速度是一样的 for (int i = 1; i <= 50000; i++) { //这样是以x从小到大排序的 sort(a[i].begin(), a[i].end()); //对y也要从小到大排序,这和整一个Node{x,y},然后自定义sort operator < 定义先按x,后按y是一个意思,有时间再写一个结构体版本的 a[i].erase(unique(a[i].begin(), a[i].end()), a[i].end()); //对于相同的(x,y)进行排序+去重 } //开始每一轮计算的主函数solve puts(solve() ? "YES" : "NO"); //存在题目要求的答案,返回YES,否则返回NO } return 0; }