首先注意到:任何合法方案一定能调整成:每种位数选一个关键点,每条边都至少有一个关键点。
本质上是希望找一个边和点的匹配。
一种思路是确定关键点之间形成的树后(暴力枚举),让每条边匹配一个关键点。
但关键点是没有连边限制的,所以是要和有限个非关键点匹配。建立二分图匹配的网络流模型:源点向左部的边连 cnt 的流量,每条边向右部的两个点连一条边,点向汇连 cnt 的流量。
另一种思路是发现可以用 Hall 定理判断状态是否合法:确定根后是边和儿子的匹配,任意点集不小于边集即可。枚举所有本质不同的方案。
实际上不需要确定根,只需要保证点集大小大于边集大小。只需要不断枚举一种边加入判断是否合法。
时间复杂度都是能过。
思路二的代码:
#include <bits/stdc++.h>
using namespace std;
const int M=6;
int n,m,p[M],s[M],a[M][M];
int cnt(int x) {return x?cnt(x/10)+1:0;}
bool chk(){
for(int i=0;i<1<<m;i++){
int v=0,e=0;
for(int j=0;j<m;j++) if(i>>j&1) v+=s[j];
if(!v) continue;
for(int j=0;j<m;j++) if(i>>j&1) for(int k=0;k<m;k++) if(i>>k&1) e+=a[j][k];
if(v<=e) return 0;
}
return 1;
}
bool upd(){
for(int i=0;i<m;i++) for(int j=0;j<m;j++) if(s[i]&&(a[i][j]||a[j][i])){
int&v=a[i][j]?a[i][j]:a[j][i];
v--;s[i]--;
if(chk()) return printf("%d %d\n",p[i]+s[i],p[j]),1;
v++;s[i]++;
}
return 0;
}
int main(){
cin.tie(0)->sync_with_stdio(0);
cin>>n;
m=cnt(n);p[0]=1;
for(int i=1;i<=n;i++) s[cnt(i)-1]++;
for(int i=1;i<m;i++) p[i]=p[i-1]*10;
for(int i=1;i<n;i++){
string u,v;
cin>>u>>v;
a[u.size()-1][v.size()-1]++;
}
if(!chk()) return puts("-1"),0;
while(upd());
return 0;
}
标签:cnt,匹配,int,Tree,return,枚举,Forgotten,New,关键点
From: https://www.cnblogs.com/shrshrshr/p/17150165.html