题目
思路
做过类似的题,有试图容斥。即 [ S = ∅ ] = ∑ T ⫅ S ( − 1 ) ∣ T ∣ [S=\varnothing]=\sum_{T\subseteqq S}(-1)^{|T|} [S=∅]=∑T⫅S(−1)∣T∣,二项式反演,用这个式子翻译 “交集为空”。
本来考虑 min-max \text{min-max} min-max 反演,而后发现不对。
最后还是要 回归原问题的性质:求最小 w i + w j w_i+w_j wi+wj 。所以我们只需找到最小的 w j w_j wj 。二分 后变为判定性问题,只需数个数。于是线段树上二分, s e t \tt set set 维护桶,复杂度 O ( n 2 m log 2 n ) \mathcal O(n2^m\log^2n) O(n2mlog2n),空间复杂度 O ( n 2 m log n ) \mathcal O(n2^m\log n) O(n2mlogn) 。先是 M L E \rm MLE MLE,后来又 T L E \rm TLE TLE,确实卡不过去。
很可惜,我对原问题的性质的发掘还不足——找的是全局最小值,而非每个 w i w_i wi 都必须求解最小 w j w_j wj 。所以可以 双指针。时间复杂度 O ( n 2 m log n ) \mathcal O(n2^m\log n) O(n2mlogn),空间复杂度 O ( n 2 m ) \mathcal O(n2^m) O(n2m) 。
然而这仍然不能通过!该死的
CF
\textrm{CF}
CF 这都要卡是吧!必须把
log
n
\log n
logn 给搞掉。最简单的方式是
unordered
map
\texttt{unordered map}
unordered map,只要你的哈希值足够微妙就可以通过(如果是
CF
\textrm{CF}
CF 就要冒着被
h
a
c
k
\rm hack
hack 的风险,除非你使用随机哈希)。复杂但正确的方式是,用
t
r
i
e
\tt trie
trie 维护。
直觉上,时空复杂度皆为 O ( n 2 m m ) \mathcal O(n2^mm) O(n2mm) 。可仔细研究后发现,将 m m m 个元素的每个子集都插入 t r i e \tt trie trie 是 O ( 2 m ) \mathcal O(2^m) O(2m) 的(查询复杂度同理),即每次找到 l o w b i t \tt lowbit lowbit,去掉 l o w b i t \tt lowbit lowbit 对应的子集已经在 t r i e \tt trie trie 中,直接在其后接上 l o w b i t \tt lowbit lowbit 就行。时间、空间都是 O ( 2 m ) \mathcal O(2^m) O(2m) 。(可是儿子数组还是要用 unordered map \texttt{unordered map} unordered map 维护……)
代码
为了避免代码过长,这里使用了哈希版。
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
#include <unordered_map> // I hate this motherf**ker
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
typedef unsigned long long ullong;
const unsigned RAD = 19260817;
ullong getHash(int a[], int len){
ullong v = 0;
for(int i=0; i!=len; ++i)
v = RAD*v+unsigned(a[i]);
return v;
}
const int MAXN = 100005, MAXM = 5;
int n, m, a[MAXN][MAXM+1];
ullong s[1<<MAXM]; int len, cnt[1<<MAXM];
void got(int id){
static int sta[MAXM], top;
for(int S=1; S!=(1<<m); ++S){
for(int j=top=0; j!=m; ++j) if(S>>j&1)
sta[top ++] = a[id][j];
s[S-1] = getHash(sta,top);
}
}
std::unordered_map<ullong,int> mp;
void insert(){ for(int j=0; j!=len; ++j) ++ mp[s[j]]; }
void erase(){ for(int j=0; j!=len; ++j) -- mp[s[j]]; }
inline int query(){
int now = 0;
for(int i=0; i!=len; ++i){
if(!mp.count(s[i])) continue;
if(cnt[i]&1) now += mp[s[i]];
else now -= mp[s[i]];
}
return now;
}
bool cmp(const int &x, const int &y){
return a[x][m] < a[y][m];
}
int id[MAXN];
int main(){
n = readint(), m = readint();
rep(i,1,n) rep(j,0,m) a[i][j] = readint();
len = (1<<m)-1; // fixed value
for(int S=1; S!=(1<<m); ++S)
cnt[S-1] = __builtin_popcount(S);
rep(i,1,n){
std::sort(a[i],a[i]+m);
got(i), insert(); // insert all
}
rep(i,1,n) id[i] = i;
std::sort(id+1,id+n+1,cmp);
int ans = 0x7fffffff;
for(int *i=id+1,j=n; i!=id+n+1; ++i)
for(got(*i); query()!=j; --j,got(*i)){
ans = std::min(ans,a[id[j]][m]+a[*i][m]);
got(id[j]), erase(); // remove
}
if(ans == 0x7fffffff) ans = -1;
printf("%d\n",ans);
return 0;
}