[NC1100C]小w的魔术扑克

mac2024-05-28  46

题目

传送门 to nowcoder

题目描述 小 w w w 有一副魔术扑克——每张扑克既有正面、又有反面。共 k k k 张牌。他每一张牌都可以选择某一面打出。现在问是否存在一种方案,能够打出 [ l , r ] [l,r] [l,r] 的顺子。

输入输出格式 我忘了。

数据范围与约定 1 ≤ l ≤ r ≤ n ≤ 1 0 5 , k ≤ 1 0 5 1\le l\le r\le n\le 10^5,k\le 10^5 1lrn105,k105

思路壹

将每一个扑克牌两面的数字当做一条边的两个端点。那么每个点都代表一个数字。一条边可以选择“搞定”一个端点。某个点被“搞定”,就是某个数字可以被打出。

首先是这样的:

对于一个联通的图,如果至少有一个点已经被搞定,并且没有用这个图内的边,那么这个图的点都可以被搞定。

原因很简单:

对这个图求一个生成树,将已经被搞定的点作为树根。每条边“搞定”子节点。

所以接下来又有这样的东西:

如果一个 n n n 个点的联通图有不小于 n n n 条边,那么整个图都可以被搞定。

原因很简单:

图里肯定有环(如果没有环就会是树,就只有 n − 1 n-1 n1 条边)。找到这个环。环上的点可以只用环上的边“搞定”(显然)。去掉一些边,让原图变成基环树(环就是找到的这个环)。发现每棵树都是已经有一个点被“搞定”的图。由前面的结论,整个图都会被搞定。

与这个结论是相辅的是:

如果一个 n n n 个点的联通图有少于 n n n 条边,那么整张图无法全部被搞定。

因为:

n n n 个点至少要 n n n 条边嘛!(就是说,你要打 [ l , r ] [l,r] [l,r] 的顺子,你至少得有 r − l + 1 r-l+1 rl+1 张牌)。

所以直接找边数小于 n n n 的联通子图——也就是树。

这个树中一旦某个点不在顺子的考虑范围中,就可以认为它已经被“搞定”。由前面的结论,一定可以搞定整棵树。

所以这棵树中的点无法被“搞定”,当且仅当 [ m i n , m a x ] ∈ [ l , r ] [min,max]\in[l,r] [min,max][l,r](也就是整个都包含在顺子的查询区间内)。这等价于 l ≤ m i n ∧ m a x ≤ r l\le min\wedge max\le r lminmaxr

所以将查询排序(右端点递增),本次查询内满足 m a x ≤ r max\le r maxr 的只会越来越多。

现在就要 [ l , r ] [l,r] [l,r] 中没有任何一个 m i n min min 。因为我们已经满足 m a x ≤ r max\le r maxr ,所以不能让 l ≤ m i n l\le min lmin 。就是说, ∀ m i n < l \forall min<l min<l 。而 m i n ≤ m a x ≤ r min\le max\le r minmaxr ,所以等价于 m i n ∉ [ l , r ] min\not\in [l,r] min[l,r] 。用树状数组就好了。

代码壹

#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <set> using namespace std; const int MaxN = 100005; int n, k, q; int fa[MaxN], cntNode[MaxN], cntEdge[MaxN]; int minV[MaxN], maxV[MaxN], cntTree; inline int findFa(int a){ if(fa[a] != a) fa[a] = findFa(fa[a]); return fa[a]; } struct PPL{ int l, r, id; bool operator<(const PPL &that)const{ return r < that.r; } }tree[MaxN], query[MaxN]; void init(){ scanf("%d %d",&n,&k); for(int i=1; i<=n; ++i){ fa[i] = i; cntEdge[i] = 0; cntNode[i] = 1; minV[i] = maxV[i] = i; } for(int i=1,x,y; i<=k; ++i){ scanf("%d %d",&x,&y); x = findFa(x); y = findFa(y); ++ cntEdge[x]; if(x != y){ cntEdge[x] += cntEdge[y]; cntNode[x] += cntNode[y]; fa[y] = x; minV[x] = min(minV[x],minV[y]); maxV[x] = max(maxV[x],maxV[y]); } } cntTree = 0; for(int i=1; i<=n; ++i) if(fa[i] == i and cntNode[i] == cntEdge[i]+1){ ++ cntTree; tree[cntTree].l = minV[i]; tree[cntTree].r = maxV[i]; } sort(tree+1,tree+cntTree+1); scanf("%d",&q); for(int i=1,a,b; i<=q; ++i){ scanf("%d %d",&a,&b); query[i].l = a; query[i].r = b; query[i].id = i; } sort(query+1,query+q+1); } int BIT[MaxN]; void modifyBIT(int id,int v){ for(; id<=n; id+=(id&-id)) BIT[id] += v; } int queryBIT(int id){ int sigma = 0; for(; id; id-=(id&-id)) sigma += BIT[id]; return sigma; } int answer[MaxN]; void work(){ int top = 1; for(int i=1; i<=q; ++i){ while(top <= cntTree and tree[top].r <= query[i].r) modifyBIT(tree[top ++].l,1); answer[query[i].id] = queryBIT(query[i].r)-queryBIT(query[i].l-1); } for(int i=1; i<=q; ++i) if(not answer[i]) puts("Yes"); else puts("No"); } int main(){ init(); work(); return 0; }

鸣谢

这位大佬的博客给了我思路。

思路贰

二分图匹配!直接将扑克与数值相连。

匈牙利算法中每次都会确保以前匹配的还是被匹配。

所以将数值作为主体。

代码贰

#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; const int MaxN = 100005; int n, k, q; vector<int> g[MaxN]; struct Query{ int l, r, id; bool operator<(const Query &that)const{ if(l != that.l) return l < that.l; return r < that.r; } }query[MaxN]; int answer[MaxN]; void init(){ scanf("%d %d",&n,&k); for(int i=1,x,y; i<=k; ++i){ scanf("%d %d",&x,&y); g[x].push_back(i); g[y].push_back(i); } scanf("%d",&q); for(int i=1; i<=q; ++i){ scanf("%d %d",&query[i].l,&query[i].r); query[i].id = i; } sort(query+1,query+q+1); } bool vis[MaxN]; int match[MaxN], ppl[MaxN]; vector<int> visited; bool XYL(int x){ vis[x] = true; visited.push_back(x); for(int i=0,len=g[x].size(); i<len; ++i) if(not match[g[x][i]] or (not vis[match[g[x][i]]] and XYL(match[g[x][i]]))){ match[g[x][i]] = x; ppl[x] = g[x][i]; return true; } return false; } void release(int x){ match[ppl[x]] = 0; ppl[x] = 0; } void solve(){ for(int i=1,nowl=1,nowr=0; i<=q; ){ while(nowl < query[i].l and nowl <= nowr) release(nowl ++); if(nowl == nowr+1){ nowl = query[i].l; nowr = query[i].l-1; } for(bool f=XYL(nowr+1); true; f=XYL(nowr+1)){ if(f) ++ nowr; while(not visited.empty()){ vis[visited.back()] = false; visited.pop_back(); } if(not f) break; } for(; i<=q and query[i].l==nowl; ++i) answer[query[i].id] = (query[i].r <= nowr); } for(int i=1; i<=q; ++i) puts(answer[i]?"Yes":"No"); } int main(){ init(); solve(); return 0; }
最新回复(0)