NOI Online 提高组 2022 解题报告
我认为本场 NOI Online 是正确的,客观的,合理的,明晰的,真实的,辩证的,深刻的,通达的,优美的,巧妙的,精辟的,雅正的,机智的,全面的,明白晓畅的,不偏不倚的,恰如其分的,滴水不漏的,不容质疑的,切中要害的,一针见血的,淋漓尽致的,深谙事理的,真知灼见的,发蒙振聩的,微言大义的,金声玉振的,形而上学的,透过现象看本质的,知其然而知其所以然的,可供世人仿效的,千古颠扑不破的。
一个出了两个数点题,却没有一个 DP/字符串/数学 的 round。
A.丹钓战
有
n 个二元组(a_i, b_i) ,编号为1 到n 。有一个初始为空的栈
S ,向其中加入元素(a_i, b_i) 时,先不断弹出栈顶元素直至栈空或栈顶元素(a_j , b_j) 满足a_i \neq a_j 且b_i < b_j ,然后再将其加入栈中。如果一个二元组入栈后栈内只有这一个元素,则称该二元组是“成功的”。
有
q 个询问[l_i, r_i] ,表示若将编号在[l_i, r_i] 中的二元组按编号从小到大依次入栈,会有多少个二元组是“成功的”。询问之间相互独立。
1 \leq n, q \leq 5 \times 10^5
思路
首先观察一下这个题的性质,记在
故我们不妨先对所有
所以我们可以把
时间复杂度
代码
const int N = 5e5 + 10;
int s[N],t[N],n,m,a[N],b[N],top,p[N],ans[N];
pii w[N];
struct Query {
int l,r,pos;
friend inline bool operator < (Query &a,Query &b) {
return a.l < b.l;
}
}q[N];
int lowbit(int x) {return (x & (-x));}
void modify(int x,int v) {
for (;x <= n;x += lowbit(x)) t[x] += v;
}
int query(int x) {
int ans = 0;
for (;x;x -= lowbit(x)) ans += t[x];
return ans;
}
signed main() {
read(n,m);
for (int i = 1;i <= n;++i) read(a[i]);
for (int i = 1;i <= n;++i) read(b[i]);
for (int i = 1;i <= n;++i) {
while (top && (a[s[top]] == a[i] || b[s[top]] <= b[i])) --top;
p[i] = s[top];
s[++top] = i;
w[i] = mp(p[i],i);
}
sort(w+1,w+1+n);
for (int i = 1;i <= m;++i) {
read(q[i].l,q[i].r);q[i].pos = i;
}
sort(q+1,q+1+m);
int now = 1;
for (int i = 1;i <= m;++i) {
while (now <= n && w[now].first < q[i].l) modify(w[now].second,1),++now;
ans[q[i].pos] = query(q[i].r) - query(q[i].l-1);
}
for (int i = 1;i <= m;++i) {
printf("%d\n",ans[i]);
}
return 0;
}
B.讨论
有
n 个人正在打模拟赛,模拟赛有n 道题目。 有两人都会的题目并且没有人会的题目包含另一个人时,两者之间才会讨论。(定义第
i 个人会的题目的集合为S_i ,即当S_x\cap S_y\neq\varnothing\land S_x\not\subseteq S_y\land S_y\not\subseteq S_x 时,第x 人和第y 人会讨论)为了让模拟赛的效果更好,希望你可以找出一对会讨论的人或判断不存在。
m=\sum k_i ,1\le n\le {10}^6 ,1\le m\le 2\times {10}^6
思路
首先按照集合的大小从大到小排序,依次处理每个集合。
我们可以维护一个数组
现在就来解决如何查询
第一种方法:
比如
每个集合只会被塞入一次,也只会被检查和删除一次,所以复杂度是
第二种方法:
我们依然采用最上边上面维护
- 若存在
f_x = 0 则说明这个集合相较之前的所有集合有新元素。 - 若至少存在两个不同的
f_x ,那么相对于题量较少的f_x ,S 肯定是有新题的。因为对于题量少的那个f_x ,其必然不会另一道交叉的题。
所以我们只要记录和谁有共同题目,然后最终的答案就是题量较少的那个集合以及当前集合。时间复杂度同第一种方法。
代码
这里给出第二种方法的参考实现:
const int N = 1e6 + 10;
int n,k[N],c[N],f[N];
vector <int> p[N];
bool cmp(int x,int y) {
return k[x] > k[y];
}
void solve() {
read(n);
memset(f,0,sizeof f);
for (int i = 1;i <= n;++i) {
read(k[i]);c[i] = i;
p[i].clear();
for (int j = 1;j <= k[i];++j) {
int x;read(x);
p[i].push_back(x);
}
}
sort(c+1,c+1+n,cmp);
bool flag = 1;
int ans1,ans2;
for (int i = 1;i <= n;++i) {
int x = c[i];
int f1=0,f2=0;
if (!flag) break;
for (auto y : p[x]) {
int las = f[y];f[y] = x;
if (!las) las = x;
if (!f1) f1 = las;
else if (f1 != las) f2 = las;
if (f1&&f2) {
if (f1 != x && f2 != x) {
//printf("%d %d %d\n",f1,f2,x);
if (k[f1] > k[f2]) f1 = x;
else f2 = x;
}
ans1 = f1,ans2 = f2;
flag = 0;
break;
}
}
}
if (flag) puts("NO");
else {
puts("YES");
printf("%d %d\n",ans1,ans2);
}
}
signed main() {
int T;read(T);
while (T--) solve();
return 0;
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。