弦图和区间图
一直想看CDQ的课件,但是一直畏惧课件的长度没敢看,今天终于看完啦
还是很简单的嘛 八月份!三个月!吼吼!
做一些总结吧,以下是本人的理解:
1、设图的点集为V,边集为E
则其诱导子图的点集为V‘,边集为E’
满足V‘属于V,E’属于E,且E‘中任意边的两个端点均属于V’
2、弦是一个环中不在环上的且连接环上两点的边
弦图中任意长度>3的环一定存在至少一条弦
换句话说,弦图中最多只有三元环,其余的大环都是小环凑成的
3、团:若对于任意两点u,v属于V,都存在(u,v)属于E
则称这个图为一个团
4、单纯点:设点u,跟u相连的点为N(u)
若存在V‘=u U N(u),使得形成的原图的诱导子图是一个团
则称u是一个单纯点
5、完美消除序列:设V的一个排列{v1,……,vn}
使得任意一个下标i均满足vi在{vi,……vn}的诱导子图是一个单纯点的序列
我们称之为完美消除序列
6、如何判断一个图是否是弦图
定理:一个图是弦图当且仅当它有一个完美消除序列
证明大略:
充分性
先证明弦图至少有一个单纯点和弦图的诱导子图也是弦图
之后运用数学归纳法假设<n的弦图具有完美消除序列,则当前的弦图的完美消除序列可以通过一个单纯点和<n的弦图的完美消除序列得到
必要性
设一个图不是弦图,那这个图至少存在一个>3的小环
设v,v1,v2是环上三点,v和v1,v2相连,v1和v2不相连(长度>3)
设v在完美消除序列中第一次出现,由于v1和v2不相连,所以v不是{v,……,vn}的单纯点
对于充分性的证明可以启发我们采用MCS算法来求弦图的完美消除序列
MCS算法大意如下:
给每个点定义lebel值表示有多少个已选择的点和其相连
逆序构建完美消除序列,每次选择未选择的label值最高的点放入完美消除序列中
同时更新label值
如何判断一个完美消除序列是否合法?
我们考虑逆序扫完美消除序列,假设我们扫到vi
只需要判断vi在{vi……vn}是否是一个单纯点即可
暴力判断显然不行
设跟vi在{vi……vn}相连的点为vj1……vjk(按完美消除序列顺序)
那么不难发现我们已经检查过vj1的单纯点性质了
我们只需要考虑vj1是否和vj2……vjk相连即可
这样判断总时间复杂度O(N+M)
CDQ的课件中说MCS的时间复杂度为O(N+M),但是不清楚桶的做法
所以我最多只会O(NlogN+M)的做法(用堆)
下面是一道模板题:ZOJ 1015 O(n^2)
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; const int maxn=1010; int n,m,u,v; int h[maxn],cnt=0; struct edge{ int to,next; }G[2000010]; int pos[maxn],check[maxn]; bool vis[maxn]; void add(int x,int y){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt; } void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void Clear(){ memset(pos,0,sizeof(pos)); memset(check,0,sizeof(check)); memset(vis,false,sizeof(vis)); } bool MCS(){ for(int now=n;now>=1;--now){ int mx=-1,mn=23333; int A=0,B=0; for(int i=1;i<=n;++i)if(!pos[i]&&check[i]>mx)mx=check[i],A=i; pos[A]=now; for(int i=h[A];i;i=G[i].next){ int v=G[i].to; if(!pos[v])check[v]++; if(pos[v]>pos[A]&&pos[v]<mn)mn=pos[v],B=v; } for(int i=h[B];i;i=G[i].next){ int v=G[i].to; vis[v]=true; }vis[B]=true; for(int i=h[A];i;i=G[i].next){ int v=G[i].to; if(pos[v]>pos[A]&&!vis[v])return false; } for(int i=h[B];i;i=G[i].next){ int v=G[i].to; vis[v]=false; }vis[B]=false; }return true; } int main(){ while(scanf("%d%d",&n,&m)==2){ if(!n&&!m)break; memset(h,0,sizeof(h));cnt=0; for(int i=1;i<=m;++i){ read(u);read(v); add(u,v);add(v,u); } Clear(); if(MCS())printf("Perfect\n\n"); else printf("Imperfect\n\n"); }return 0; }
7、弦图染色问题
直接贪心就可以了,逆序扫完美消除序列
给每个点染能染的最小颜色,分组的答案是颜色的最大值
时间复杂度O(N+M)
HNOI 神奇的国度
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; const int maxn=10010; int n,m,u,v; int ans; int h[maxn],cnt=0; int pos[maxn],fp[maxn]; int check[maxn]; int vis[maxn],c[maxn]; struct edge{ int to,next; }G[2000010]; void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void add(int x,int y){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt; } void MCS(){ for(int now=n;now>=1;--now){ int mx=-1,A=0; for(int i=1;i<=n;++i)if(!pos[i]&&check[i]>mx)mx=check[i],A=i; pos[A]=now;fp[now]=A; for(int i=h[A];i;i=G[i].next){ int v=G[i].to; if(!pos[v])check[v]++; } }return; } void Get_ans(){ for(int now=n;now>=1;--now){ int u=fp[now]; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; vis[c[v]]=now; } for(int i=1;i;++i){ if(vis[i]!=now){c[u]=i;break;} } } for(int i=1;i<=n;++i)ans=max(ans,c[i]); } int main(){ read(n);read(m); for(int i=1;i<=m;++i){ read(u);read(v); add(u,v);add(v,u); } MCS();Get_ans(); printf("%d\n",ans); return 0; }
8、弦图的最大独立集问题和最小团覆盖
很容易知道最大独立集=最小团覆盖
最大独立集就是正序扫完美消除序列,能选就选
9、区间图
将有重叠部分的区间之间建边,形成区间图
可以证明区间图一定是弦图
所以我们可以用弦图的做法求区间的各种东西
另外值得一提的是区间图可以不用MCS求完美消除序列
将区间按右端点从小到大排序后的结果就是完美消除序列
cojs 简单的区间问题
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=100010; int n; struct Pos{ int L,R; }c[maxn]; int vis[maxn]; int col[maxn]; bool cmp(const Pos &A,const Pos &B){ if(A.R==B.R)return A.L>B.L; return A.R<B.R; } void read(int &num){ num=0;char ch=getchar(); while(ch<'!')ch=getchar(); while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); } void Solve_one(){ int R=-1,ans=0; for(int i=1;i<=n;++i){ if(c[i].L>R)ans++,R=c[i].R; }printf("%d\n",ans); } void Solve_two(){ int ans=0; for(int i=n;i>=1;--i){ for(int j=i+1;j<=n;++j){ if(c[j].L<=c[i].R)vis[col[j]]=i; } for(int j=1;j;j++){ if(vis[j]!=i){col[i]=j;break;} }ans=max(ans,col[i]); }printf("%d\n",ans); } int main(){ freopen("get_pos.in","r",stdin); freopen("get_pos.out","w",stdout); read(n); for(int i=1;i<=n;++i)read(c[i].L),read(c[i].R); sort(c+1,c+n+1,cmp); Solve_one();Solve_two(); return 0; }
CDQ的PPT还是没看完,先总结这么多吧