题目大意
给定m个数的一个非负整数集合,不超过30。你需要构造一个竞赛图,满足:所有点的出度去重后等于该集合。
m≤31
分析
做这题我用到了兰道定理(Landau’s Theorem)。
设点i的出度为d[i],那么对于任意 1≤i≤n ,有 ∑ij=1d[i]≥i(i−1)2 。且当i=n时是等于。
那么可以考虑构造一个合法的d[]数组。每个数至少用一次,那么可以做一次背包。然后枚举点的个数,判断是否存在即可。
得到出度数组d[]后,依然是利用兰道定理来构造出竞赛图。主要思路如下:
1. 对于i>j,i向j连边,得到一个最初始的竞赛图,记它的出度数组为u[]。并且给d[]升序排序
2. 找到第一个满足 d[i]>u[i] 的位置,然后找到最大的j,满足u[i]=u[j],接下来找到第一个满足 d[k]<u[k] 的位置,有 j<k 且 u[j]+2≤u[k] ,那么必然存在点x满足k向x连边且x向j连边。把这两条边翻转即可
3. 重复上面的过程直到d[]与u[]完全相同
关于兰道定理的具体内容可以上网搜
#include <cstdio>
#include <cstring>
#include <algorithm>using namespace std;const int N=62,M=1832;typedef long long LL;int n,m,a[N],f[N][N][M],g[N][N][M],d[N],u[N];bool G[N][N];int main()
{scanf("%d",&m);for (int i=1;i<=m;i++) scanf("%d",&a[i]);sort(a+1,a+m+1);f[0][0][0]=1;for (int i=1;i<=m;i++)for (int j=i;j<N;j++)for (int k=i-1;k<j;k++)for (int y=k*(k-1)/2,x=(j-k)*a[i]+y;x<M;x++,y++) if (f[i-1][k][y]){f[i][j][x]=1; g[i][j][x]=j-k;}for (n=m;n<N && !f[m][n][n*(n-1)/2];n++);if (n==N){printf("=(\n"); return 0;}printf("%d\n",n);for (int i=m,j=n,k=n*(n-1)/2,x;i;i--){for (x=0;x<g[i][j][k];x++) d[j-x]=a[i];x=k; k-=g[i][j][x]*a[i]; j-=g[i][j][x];}sort(d+1,d+n+1);for (int i=1;i<=n;i++){u[i]=i-1; for (int j=1;j<i;j++) G[i][j]=1;}for (int i,j,k,x;;){for (i=1;i<=n && d[i]<=u[i];i++);if (i>n) break;for (j=n;u[j]!=u[i];j--);for (k=1;d[k]>=u[k];k++);for (x=n;!(G[k][x] && G[x][j]);x--);G[k][x]=0; G[x][k]=1; G[x][j]=0; G[j][x]=1; u[k]--; u[j]++;}for (int i=1;i<=n;i++){for (int j=1;j<=n;j++) putchar(G[i][j]+'0');printf("\n");}return 0;
}