前言
想想自己做, 一共就两种 \(\rm{trick}\) 还不会?
思路
你发现两个不能和谐共处的奶牛, 当且仅当他们的 \(10\) 个喜好不重
因为要求时间复杂度不能是 \(\mathcal{O} (n^2)\) , 所以肯定要想办法做到不枚举点对
这个时候联想到之前的一道题 [CEOI2010 day2] pin, 我们考虑利用相似的方法 (结果之前那题是水过去的, 又去研究了一遍花费 \(10 \ \rm{min}\))
我们考虑「钦定」当前这只奶牛的 \(k\) 个喜好与其他奶牛相同, 这个时候我们可以轻松的 \(\mathcal{O}(1)\) 统计出有多少只, 仅仅需要使用 \(\rm{hash}\)
其中统计的总时间复杂度是 \(\mathcal{O} (2^{\omega} n)\) , 其中 \(\omega = 5\)
这不无敌了, 你发现令 \(f(k)\) 为「至多」\(k\) 个喜好与其他奶牛不同的对数, 可以快速计算出来 (即「钦定」\(\omega - k\) 个位置相同), 套路的, 令 \(g(k)\) 为「恰好」\(k\) 个喜好与其他奶牛不同的对数
容易发现
实现
\(\rm{TJ}\) 区并没有这种做法, 只能自己多考虑一下打出来了
框架
首先是计算 \(f\)
我们需要在枚举的过程中, 用二进制枚举的方式处理相同的喜好集合, 然后统计进 \(\rm{hash}\)
6, 注意到不能用 \(\rm{vector}\) , 而应该使用 \(\rm{set}\)
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 5e4 + 20;int n;
int cows[MAXN][5];std::map<std::set<int>, int> hash; // 映射 hashint f[6];
/*计算 f(i)*/
void fcalc() {for (int i = 1; i <= n; i++) for (int s = 0; s < 32; s++) { // 枚举位置 / 状态std::set<int> ret; ret.clear(); for (int j = 0; j < 5; j++) if ((s >> j) & 1) ret.insert(cows[i][j]);f[5 - __builtin_popcount(s)] += hash[ret]; hash[ret]++;}
}signed main()
{scanf("%lld", &n);for (int i = 1; i <= n; i++) for (int j = 0; j < 5; j++) scanf("%lld", &cows[i][j]);fcalc();int ans = 0;for (int i = 0; i <= 5; i++) {ans += (i % 2 ? 1ll : -1ll) * f[i];}printf("%lld", ans);return 0;
}
总结
很像啊, 基本上逻辑相通
也算是常见 \(\rm{trick}\) , 本质上是观察到用 \(\rm{hash}\) 计算对数可以做到更快, 而且只有 \(\omega = 5\) 方便处理子集