LuoguP8252 [NOI Online 2022 提高组] 讨论
题意简述
给定 YES
,并输出 NO
。带多测。
对于所有测试点:令一组数据中
解题思路
前置方法
如何判断两个集合
- 遍历
里面的每一个元素,若在 里面皆存在,那么 。只要保证 有序( ),就可以用std::lower_bound
来求解。这样的复杂度是 ,适合处理 小 大的极端情况。 - 先保证
皆有序。遍历 ,并记一个指向 开头的指针 。如果在 中遇到 ,就令 。如果发现 的话,一定不包含。如果在遍历中或结束时, 已经被 遍历完了,那么就包含。其他情况都是不包含。这种方法的复杂度是 ,适合处理 差距不大的情况。
暴力
发现两个集合要满足的条件是两类:①交集非空与②非包含关系。因此我们把两类条件分开来考虑。
发现要满足条件一很容易。我们可以开一个桶
那么我们就能想出暴力的方法:遍历
这样做的复杂度比较高,是
优化 1
由于题目中只让我们找到一个不满足条件的二元组,我们可以考虑以下优化。
将
一个细节是:这个排序应当是稳定的。否则效率会变低。这里不再赘述,请读者思考。
因此我们只需考虑
这样做,复杂度是
优化 2
我们发现需要比较的次数太多了。如何减少呢?
我们考虑把每个人看做一个点。当两个点进行比较时,就在两点间连一条有向边。最后我们遍历图即可。
但是实际上比较次数还是一样多的。我们发现这个图和可持久化 trie 的结构一样:从每个点出发,能到达的点与经过的边是一棵树。于是可以考虑 dfs。
假设当前的点是
当然,如果你想优化求并集,这当然是可以的。采用分治即可。
还有一个小优化:每个点都很有可能大量重复到另外一个点,因此要 std::unique
一下。当然用桶排处理也可以。
复杂度证明
每个点只会比较一次。因此是
关键是求并集。它的复杂度好像很假,不是吗?
最劣的构造是这样的:一个点
但是我们发现,它的入度至多是
用分治求并集,复杂度会玄学一些,我不会证,但上限似乎还是
总的复杂度应该是
当然,你还可以做许多剪枝来优化这个过程,这里不再赘述。
程序实现
我的这份代码有很多提到的优化没有用,而且常数比较大。如果把我提到的优化写完并且常数小一些,控制在 200ms 应该是没问题的。需要 C++ 11 及以上标准。
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<utility>
#include<set>
#define PAI pair<int,int>
#define val first
#define pos second
#define mkp make_pair
#define VI vector<int>::iterator
#define SI set<int>::iterator
#define R myio::read_int()
using namespace std;
namespace myio{
int read_int(){
int x=0;char ch,f=1;
while(!isdigit(ch=getchar())) if(ch=='-') f=0;
do x=(x<<1)+(x<<3)+ch-'0';
while(isdigit(ch=getchar()));
return (f==1?x:-x);
}void PRINT(int x){
if(x<=9) putchar(x+'0');
else PRINT(x/10),putchar(x%10+'0');
}void print_int(int x){
if(x<0) putchar('-'),PRINT(-x);
else PRINT(x);
putchar(' ');
}
}
const int N=1e6+6,P=998244353;
int T,n,tp,X,Y,ys[N],error;
vector<int> stu[N];
vector<PAI> ton[N];
vector<int> e[N];
void Clear() {
for(int i=1;i<=n;i++) ton[i].clear();
for(int i=1;i<=n;i++) stu[i].clear();
for(int i=1;i<=n;i++) e[i].clear();
}
bool contain(set<int> &x,int y) { //stu[y] contains x
SI j=x.begin();
for(auto i:stu[y]) {
if(i==*j) j++;
if(j==x.end()) return true;
if(i>*j) {error=*j;return false;}
}error=*j;
return false;
}
void build() {
for(int i=1;i<=n;i++) if((int)ton[i].size()>=2) {
sort(ton[i].begin(),ton[i].end());
for(int j=1,sz=ton[i].size();j<sz;j++)
e[ton[i][j].pos].push_back(ton[i][j-1].pos);
}for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());
for(int i=1;i<=n;i++) e[i].erase(unique(e[i].begin(),e[i].end()),e[i].end());
}
bool cson(int u) {
set<int> bson;
for(int i:e[u]) for(int j:stu[i]) bson.insert(j);
if(contain(bson,u)) return true;
return false;
}
int dfs(int u) {
if(!cson(u)) return u;
int err;
for(int to:e[u]) {
if(!e[to].empty()) {
err=dfs(to);
if(err!=0) return err;
}
} while(!e[u].empty()) e[u].pop_back();
return 0;
}
int Check() {
for(int i=1;i<=tp;i++) {
if(e[i].empty()) continue;
int err=dfs(i);
if(err!=0) return err;
} return 0;
}
int Find(int u) {
for(auto to:e[u]) {
VI it=lower_bound(stu[to].begin(),stu[to].end(),error);
if(it!=stu[to].end()&&*it==error) return to;
} return -1;
}
signed main(){
T=R;
while(T--) {
n=R;Clear();tp=0;
for(int i=1;i<=n;i++) {
int k=R;
if(k==0) continue;
if(k==1) {k=R;continue;}
tp++;ys[tp]=i;
for(int j=1,x;j<=k;j++) {
ton[x=R].push_back(mkp(k,tp));
stu[tp].push_back(x);
}sort(stu[tp].begin(),stu[tp].end());
}build();
int err=Check();
if(err!=0) {
puts("YES");
X=ys[err],Y=ys[Find(err)];
if(X>Y) swap(X,Y);
myio::print_int(X);
myio::print_int(Y);
putchar('\n');
}else puts("NO");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律