题意
[SHOI2015]零件组装机:https://www.luogu.com.cn/problem/P4386
做法
同机房大佬想性质想了很久,我从树的思想搞很快搞出来了
言归正传,这道题目从树的思路想是比较简单的,关键是建树。
现在讲讲建树:对于一条边,默认是从编号大的连向编号小的有向边。
那么,设 $x$ 连向的编号最大的点为 $y$,那么 $x,y$ 是什么关系?
我们规定一个联通块的根为这个联通块编号最小的点(不难发现联通块的编号是连续的),那么 $x,y$ 的关系其实就是以 $x$ 为根的联通块与 $[y,x-1]$ 的联通块合并。
这样,我们就只需要根据 $x,y$ 建出一棵树,从下往上合并,每次合并检验一下即可。
需要注意的是:如果存在重边和自环直接无解。
当然还有一个性质:合法图的边的是在 $O(n)$ 级别的。(同机房大佬找到的)
时间复杂度:$O((n+m)\log{m})$
用桶排加内存池以及其余细节便可以优化到 $O(n+m)$。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include<cstdio> #include<cstring> #include<algorithm> #include<set> #define N 110000 using namespace std; set<int> fuck[N]; int cnt,now; int fa[N],siz[N]; int id[N]; inline bool cmp(int x,int y){return siz[x]<siz[y];} inline bool check(int l1,int r1,int l2,int r2) { int n=r1-l1+1,m=r2-l2+1; if(n>m)return 1; for(int i=0;i<m;i++) { int x=l1+i%n,y=l2+i; set<int>::iterator z=fuck[y].find(x); if(z==fuck[y].end())return 1; now++; fuck[y].erase(z); } return 0; } int n,m; int main() { int T;scanf("%d",&T); while(T--) { now=cnt=0; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)fuck[i].clear(); bool bk=0; for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); if(x<y)x^=y^=x^=y; if(bk)continue; if(x==y){bk=1;continue;} set<int>::iterator shit=fuck[x].find(y); if(shit==fuck[x].end()) { fuck[x].insert(y); cnt++; } else bk=1; } for(int i=0;i<n;i++)id[i]=i; siz[0]=1; for(int i=1;i<n;i++) { siz[i]=1; if(!fuck[i].size()) { bk=1; break; } set<int>::iterator x=fuck[i].end(); x--;fa[i]=*x; } if(bk==1){printf("NO\n");continue;} for(int i=n-1;i>=1;i--)siz[fa[i]]+=siz[i]; sort(id,id+n,cmp); for(int i=0;i<n-1;i++) { int x=id[i],y=fa[x]; if(check(y,x-1,x,x+siz[x]-1)) { bk=1; break; } } if(bk==1 || now!=cnt){printf("NO\n");continue;} printf("YES\n"); } return 0; }
|