cf1697 E. Coloring
题意:
用 \(n\) 种颜色给 \(n\) 个点染色,每种颜色可重复使用,不必用上所有颜色。距离为曼哈顿距离,要求:
- 若有三点同色则任两点间的距离相等
- 若点 \(a,b\) 同色而点 \(c\) 不同色,则 \(dis(a,b)<dis(a,c),dis(b,c)\)
问染色方案数取模
\(n \le 100\)
思路:
考虑把所有点划分成若干集合,每个集合中的所有点要么同色,要么全都不同色。不属于同一集合的两点必不同色。所在集合的大小为1的点称为孤立点
\(mind(i)\) 表示 \(i\) 到其他点的最短距离
如果 \(dis(i,j)=mind(i)=mind(j)\),那么 \(i,j\) 要么属于同一集合,要么都是孤立点。这一性质是 “双向” 的,我们先用它来构造初始集合
如果 \(dis(i,j)=mind(i)\),那么 \(i\) 要么是孤立的,要么在 \(j\) 的集合中;否则 \(i\) 和 \(j\) 不可能在同一集合。我们用这一性质来拆散不合法的集合
然后把所有集合的大小拿出来做dp。\(f(i,j)\) 表示处理到第 \(i\) 个集合,要用 \(j\) 种颜色。那么 \(f(i,j)=f(i-1,j-1)+f(i-1,j-|S_i|)\)
答案是 \(\sum A_n^i f(m,i)\),\(m\) 为集合总数,要乘一个排列数给它们上色。
用并查集维护集合。代码看起来繁琐,但其实构造集合的思路极其简单,后面的dp也是入门级别
void chai(int x) { //把并查集中的每个点拆成独立
for(int i = 1; i <= n; i++)
if(get(i) == x) p[i] = i;
}
void sol() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];
for(int i = 1; i <= n; i++) { //距离矩阵
mind[i] = INF; //离i最近的点的距离
for(int j = 1; j <= n; j++) if(i != j)
d[i][j] = abs(x[i]-x[j]) + abs(y[i]-y[j]),
mind[i] = min(mind[i], d[i][j]);
}
for(int i = 1; i <= n; i++) //构造集合
for(int j = i+1; j <= n; j++)
if(d[i][j] == mind[i] && d[i][j] == mind[j])
merge(i, j);
for(int i = 1; i <= n; i++) //拆散不合法的集合
for(int j = 1; j <= n; j++) if(i != j)
if(d[i][j] == mind[i] && get(i) != get(j) ||
d[i][j] != mind[i] && get(i) == get(j)) chai(get(i));
for(int i = 1; i <= n; i++) cnt[get(i)]++; //最终的集合
for(int i = 1; i <= n; i++)
if(cnt[i]) cnt[++m] = cnt[i]; //去掉零
f[0][0] = 1; for(int i = 1; i <= m; i++) //dp
for(int j = 1; j <= n; j++) {
(f[i][j] += f[i-1][j-1]) %= mod;
if(cnt[i] > 1 && j >= cnt[i])
(f[i][j] += f[i-1][j-cnt[i]]) %= mod;
}
ll ans = 0; for(int i = 1; i <= n; i++) //统计答案
(ans += A(n, i) * f[m][i] % mod) %= mod;
cout << ans;
}

浙公网安备 33010602011771号