题解 [CF611H] New Year and Forgotten Tree
给孩子整自闭了.jpg
首先发现可以将位数相同的数缩成一个集合
然后还能发现集合内部有连边的点可以直接缩成一个点
再然后就不会了
- 对于一部分构造题,尤其是给边定向一类的,考虑图匹配/网络流
以上与本题基本无关
考虑上面已经推得的结论
因为树上不能有环,所以同一集合内的点都是由外面的点连通的
那么经过一些调整可以发现可以让每个集合内只有一个点度数 \(>1\),称这个点为关键点
然后抛弃上面包括缩点在内的一切结论
令集合有 \(m\) 种
那么本质不同的边有 \(\frac{m(m-1)}{2}\) 种,本质不同的点有 \(m\) 种
将树定为有向的,令 1 为根
那么整棵树可以看做每条边与其儿子的完美匹配
那么就是一个二分图多重完美匹配
注意到点数小的离谱,而 hall 定理同样适用于多重匹配
于是枚举一种边匹配掉,hall 定理 check 匹配掉后是否还存在完美匹配即可
实现上有一些细节
我试图钦定 \(1,10,100\cdots\) 这些点为关键点,然后各种假
其实可以时刻维护出哪些点已经加入树中,每次加入一个不在树中的
让每个集合中第一个被加入的点做关键点即可省掉一堆细节
最后复杂度是 \(O(nm^22^m)\),跑不满
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 200010
#define fir first
#define sec second
#define pb push_back
#define ll long long
//#define int long long
int n, m;
char s[N], t[N];
vector<int> buc[N];
pair<int, int> sta[N];
bool vis[N], cover[7][7];
map<pair<int, int>, bool> mp;
int e[10][10], siz[N], rep[N], cnt, top;
bool check() {
// cout<<"check: "<<endl;
// cout<<"siz: "; for (int i=1; i<=m; ++i) cout<<siz[i]<<' '; cout<<endl;
// cout<<"---edge---"<<endl; for (int i=1; i<=m; ++i) {for (int j=1; j<=m; ++j) cout<<e[i][j]<<' '; cout<<endl;}
int lim=1<<m;
for (int s=1; s<lim; ++s) {
memset(cover, 0, sizeof(cover));
int x=0, y=0;
for (int i=1; i<=m; ++i) if (s&(1<<i-1)) {
for (int j=1; j<=m; ++j) cover[min(i, j)][max(i, j)]=1;
x+=siz[i];
}
for (int i=1; i<=m; ++i)
for (int j=i; j<=m; ++j) if (cover[i][j])
y+=e[i][j];
if (x>y) return 0;
}
return 1;
}
signed main()
{
scanf("%d", &n);
// cout<<"m: "<<m<<endl;
for (int i=1; i<=n; i*=10,++m);
for (int i=1; i<=n; ++i) {
int cnt=0;
for (int t=i; t; t/=10,++cnt);
siz[cnt]+=(i!=1);
if (i!=1) buc[cnt].pb(i);
}
for (int i=1,u,v; i<n; ++i) {
scanf("%s%s", s, t);
u=strlen(s), v=strlen(t);
++e[min(u, v)][max(u, v)];
}
// cout<<check()<<endl;
vis[1]=1; rep[1]=1;
for (; cnt<n-1; ++cnt) {
// cout<<"cnt: "<<cnt<<endl;
for (int i=1; i<=m; ++i) if (vis[i]) {
for (int j=1; j<=m; ++j) if (e[min(i, j)][max(i, j)]&&siz[j]) {
// cout<<"ij: "<<i<<' '<<j<<endl;
--e[min(i, j)][max(i, j)];
if (--siz[j], check()) {
sta[++top]={rep[i], buc[j].back()};
if (!vis[j]) rep[j]=buc[j].back(), vis[j]=1;
buc[j].pop_back(); goto jump;
}
else ++siz[j];
++e[min(i, j)][max(i, j)];
}
}
puts("-1"); return 0;
jump: ;
}
assert(top==n-1);
for (int i=1; i<=top; ++i) printf("%d %d\n", sta[i].fir, sta[i].sec);
return 0;
}