图的度数序列相关定理

· · 题解

一张图每个点的度数总和是偶数……

经典问题:给定一个非负整数的序列d,问是否有一个简单图使得每个点的度数恰好为这个序列。可能无解。

首先如果\sum_{i=1}^nd_i是奇数那就铁定无解了,以下默认不会出现这种情况。

Havel-Hakimi 算法

可以贪心的构造,将所有点的度数降序排序,然后每次把度数最大的点1拿出来,向其他度数次大的那些点连边。

度数序列就会从\{d_1,d_2,d_3,d_4,...,d_n\}变到\{d_2-1,d_3-1,...,d_{d_1+1}-1,d_{d_1+2},...,d_n\}

可以证明如果原来的度数序列可简单图化,那么贪心连边后的度数序列也可以简单图化,充分且必要。

Erdős–Gallai 定理

对于非递增度数序列\{d_1,d_2,\dots,d_n\},如果这个序列可简单化当且仅当

\forall 1\le k\le n,\sum_{i=1}^kd_i\le k(k-1)+\sum_{i=k+1}^nmin(k,d_i)

我也只知道个必要性:右侧展示的其实是前k个点连边的数量上界,k(k-1)是前k个点之间连边的上界,后侧的sigma是前k个点与n-k个点连边的上界。

好像还有个是否有向图化的Fulkerson–Chen–Anstee定理。我也不是很懂……

$$\forall 1\le k\le n,\sum_{i=1}^ka_i\le \sum_{i=1}^kmin(b_i,k-1)+\sum_{i=k+1}^nmin(b_i,k)$$ 例题: [CF1091E New Year and the Acquaintance Estimation](https://www.luogu.org/problemnew/show/CF1091E) 首先介绍这个题我的暴力做法…… 第一步则是想到所有度数的和应该要是偶数。 判断加入一个数后这个序列还能不能简单图化,就利用上面那个E定理,这样对于一个序列我们能$O(n)$判断是否能构成一个图。这样的话我们就能枚举答案,在$O(n^2)$时间内解决这个问题了。 但注意到每次向序列中只添加一个数,这个操作对序列的影响是较小的,所以我们考虑加入一个数后序列会有什么变化,并且我们计算的式子有什么变化。 设我们当前加入的一个数为$x$,加入$x$后序列中第一个大于等于$x$的数是原序列中的第$i$大的数。我们要计算每一个$1\le k\le n+1$,上面那个式子是否成立。 那么我们分情况讨论一下:对于$k\le i$,对于这一部分我们计算的式子是 $$\sum_{i=1}^ka_i\le min(x,k)+k(k-1)+\sum_{i=k+1}^nmin(a_i,k)$$ 发现式子的变化就只有一个$min$而已,注意到$k$是单调的,所以对于$k\le i$部分的计算又被分成了两部分: $1\le k\le x$式子是$k+k(k-1)+\sum_{i=k+1}^nmin(a_i,k)-\sum_{i=1}^ka_i$是否$\ge 0$,发现这个式子与$x$无关,所以我们可以预处理这$n$个式子的值,以及前缀最小值。 $x\le k\le i$式子是$x+k(k-1)+\sum_{i=k+1}^nmin(a_i,k)-\sum_{i=1}^ka_i$是否$\ge 0$,我们处理出除去$x$部分的式子的值,我们区间查一个最小值就行了。 接下来是$i<k\le n+1$,也是预处理式子,只不过这一部分的预处理注意插入的数的位置是$i<k$的,小细节注意一下,这一部分也是查一个后缀最小值。 那就没了,具体细节看代码吧。我的复杂度瓶颈是一个区间RMQ,最后的复杂度是$O(NlogN)$,但是注意到询问的区间是$(x,i]$,应该可以从大到小枚举$x$,这样区间就单调了,也就能$O(N)$做了…… 另外题解的做法是观察到合法的度数答案一定是一个区间,所以我们可以二分这个答案区间的两个端点,每次去check答案合不合法,这样的话可以很简单的利用单次$NlogN$的check(就是暴力用定理去算),做到$O(Nlog^2N)$,这个做法好写细节少,场上各位巨佬大多都是这么做的。 ``` #include<cstdio> #include<algorithm> using namespace std; #define N 520230 #define ll long long #define ls i<<1 #define rs i<<1|1 #define INF 0x3f3f3f3f3f3f3f3f int a[N],ans[N],cc;ll sum[N],suf[N],lml[N],lmr[N],lmk[N],tree[N<<2]; bool cmp(int a,int b) {return a>b;} void build(int i,int l,int r) { if (l==r) {tree[i]=lml[l];return;} int mid=l+r>>1; build(ls,l,mid);build(rs,mid+1,r); tree[i]=min(tree[ls],tree[rs]); } ll query(int i,int l,int r,int L,int R) { if (L<=l&&r<=R) return tree[i]; int mid=l+r>>1; if (R<=mid) return query(ls,l,mid,L,R); else if (L>mid) return query(rs,mid+1,r,L,R); return min(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R)); } inline int read(){ int n=0;char a;bool z=false; while(a=getchar()) { if (a>'9'||a<'0') if (z) break; else continue; if (!z) z=true; n=(n<<1)+(n<<3)+(a^48); } return n; } int main() { int n=read();bool Z=false; for (int i=1;i<=n;++i) a[i]=read(); sort(a+1,a+1+n,cmp); for (int i=n;i;--i) suf[i]=suf[i+1]+a[i]; for (int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i]; for (int i=1,j=n;i<=n;++i) { while(j&&a[j]<i) --j; if (j>i) lml[i]=1ll*(j-1)*i+suf[j+1]-sum[i]; else lml[i]=suf[i+1]+1ll*i*(i-1)-sum[i]; lmk[i]=lml[i]+i; } for (int i=0,j=n;i<=n;++i) { while(j&&a[j]<i+1) --j; if (j>i) lmr[i]=1ll*j*(i+1)-sum[i]+suf[j+1]; else lmr[i]=suf[i+1]+1ll*i*(i+1)-sum[i]; } build(1,1,n); lmk[0]=INF;for (int i=1;i<=n;++i) lmk[i]=min(lmk[i-1],lmk[i]); lmr[n+1]=INF;for (int i=n;i>=0;--i) lmr[i]=min(lmr[i],lmr[i+1]); for (int x=sum[n]&1,i=n;x<=n;++++x) { while(i&&x>a[i]) --i;Z=true; if (lmk[min(i,x)]<0) continue; if (x<i&&query(1,1,n,x+1,i)<-x) Z=false; if (!Z) continue; if (lmr[i]<x) Z=false; if (Z) ans[++cc]=x; } if (!cc) return !printf("-1"); for (int i=1;i<=cc;++i) printf("%d ",ans[i]); return 0; } //by qlwpc ```cpp