原题链接:poker
赛时只有 \(40\) 分,改完之后感觉是一道好题,于是就来写篇题解。
题意
有 \(k\) 张扑克牌,\(n\) 种数字,每张牌都有两面,每一面分别写了一个数字,你可以选择打出这张牌的任意一面,但是不能两面同时打,也可以选择不打这张牌。有 \(q\) 个询问,每个询问给定 \(l\) 和 \(r\),判断能否打出 \(l\) 到 \(r\) 的顺子。
数据范围:\(n,k,q<=10^{6}\)。
思路
很容易想到暴力做法,每次 \(O(k)\) 扫一遍,贪心的去选择,时间复杂度 \(O(kq)\)。
考虑如何优化。这道题优化的方法是图论建模。对于每一张牌的两个面的数字 \(u\) 和 \(v\),连一条从 \(u\) 到 \(v\) 的边。这样就有了若干个连通块。这时我们会发现一些性质:对于一个大小为 \(N\) 的连通块来说,如果其中的边数 \(>=N\),那么一定可以一次性打出连通块中所有的牌;如果边数\(=N-1\),那么一定不能一次性打出连通块中所有的牌。边数为 \(N-1\) 其实就是一棵树。
我们先用并查集找出所有的树,记录一下 \(father\) 和 \(size\) 就行了。然后对于每一棵树,记 \(l\) 表示树中最小的数字,\(r\) 表示树中最大的数字。然后每一棵树都构造一条从 \(l\) 到 \(r\) 的约束线段,只要询问中的线段包含了任意一条约束线段,那么就是不合法的,因为一次性一定打不出约束线段中的每一个数字,证明见上。
于是现在问题转化成了给定若干条约束线段,询问 \(l\) 到 \(r\) 这条线段是否包含了任意一条约束线段。首先我们可以将约束线段按照右端点排序,将询问线段也按照右端点排序。动态维护一个变量 \(maxl\) 表示右端点小于当前询问线段的所有约束线段中,左端点的最大值。这样只要当前询问线段的左端点 \(<=maxl\) 的话,那么就包含了一条约束线段,是不合法的。反之则是合法的,时间复杂度 \(O(q+k)\),因为用了排序,总时间复杂度 \(O((q+k) \log (q+k))\),可以通过。当然最后一步也可以用树状数组,线段树或者扫描线来维护,但是比较麻烦。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define inf 0x3f3f3f3f
#define inf_db 127
#define ls id << 1
#define rs id << 1 | 1
#define re register
typedef pair <int,int> pii;
const int MAXN = 1e6 + 10;
int n, k, u, v, fa[MAXN], Q, cnt, tmp = 1, deg[MAXN], to[MAXN], ans[MAXN];
pii sz[MAXN];
bool flag[MAXN];
int find(int x)
{
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
struct line
{
int l, r, id;
}a[MAXN];
inline bool Cmp(line x, line y)
{
if(x.r == y.r) return (x.l != y.l ? x.l > y.l : x.id < y.id);
return x.r < y.r;
}
signed main()
{
scanf("%lld%lld", &n, &k);
for(int i = 1;i <= n;i++) fa[i] = i;
for(int i = 1;i <= k;i++)
{
scanf("%lld%lld", &u, &v);
fa[find(u)] = find(v);
deg[u]++, deg[v]++;
}
for(int i = 1;i <= n;i++)
{
int id = find(i);
sz[id].first++, sz[id].second += deg[i];
}
for(int i = 1; i <= n; i++)
if (fa[i] == i && sz[i].first == sz[i].second / 2 + 1) to[i] = ++cnt;
for (int i = 1; i <= n; i++) a[i].l = inf;
for (int i = 1; i <= n; i++) {
a[to[fa[i]]].l = min(a[to[fa[i]]].l, i);
a[to[fa[i]]].r = max(a[to[fa[i]]].r, i);
}
cin >> Q;
for(int i = 1;i <= Q;i++) scanf("%lld%lld", &a[cnt + i].l, &a[cnt + i].r), a[cnt + i].id = i;
sort(a + 1, a + cnt + Q + 1, Cmp);int mxl = 0;
for(int i = 1;i <= cnt + Q;i++)
{
if (!a[i].id) mxl = max(mxl, a[i].l);
else ans[a[i].id] = (a[i].l > mxl);
}
for (int i = 1; i <= Q; i++) cout << (ans[i] ? "Yes" : "No") << endl;
return 0;
}
标签:poker,return,int,题解,线段,MAXN,端点,询问
From: https://www.cnblogs.com/Creeperl/p/17913449.html