L - Olympiad Training
$FWT$。首先,观察到$n$很小,我们可以想办法求出每个子集的答案,但是$m$很大,直接枚举子集的方法肯定行不通。不妨考虑对每个$topic$单独考虑贡献,设当前考虑的是第$j$个$topic$,然后枚举最大值,设$a_{ij}$为最大值,其他的人我们只能选比它小的,设我们能选出的最大的集合为$mask$,显然,对于$mask$子集$s$,如果$s$包含第$i$个人,答案就需要加上$a_{ij}$,如果不包含,它的最大值就为其他值,我们不妨分两步完成这个操作,首先,对于$mask$的所有子集加上答案$a_{ij}$,然后,设$mask'$为$mask$去掉$i$之后的集合,我们对$mask'$的所有子集减去$a_{ij}$,到此为止,如果我们能快速完成给子集同时加上一个数这个操作,这个题就做完了,$and$运算的$FWT$支持此操作。
1 #include<iostream> 2 #include<cstdio> 3 #include<vector> 4 #include<algorithm> 5 const int N=22; 6 int n,q,m; 7 typedef long long ll; 8 ll f[1<<20],ans[22]; 9 typedef std::pair<int,int> P; 10 std::vector<P> g[10004]; 11 void FWT(ll *a,int n) { 12 for(int i=1;i<n;i<<=1) 13 for(int p=i<<1,j=0;j<n;j+=p) 14 for(int k=0;k<i;k++) 15 a[j+k]+=a[i+j+k]; 16 } 17 int main() { 18 int T; 19 scanf("%d",&T); 20 while(T--) { 21 scanf("%d%d%d",&n,&m,&q); 22 for(int i=0;i<1<<n;i++) f[i]=0; 23 for(int i=0;i<m;i++) g[i].clear(); 24 for(int i=0;i<n;i++) { 25 for(int j=0,x;j<m;j++) { 26 scanf("%d",&x); 27 g[j].push_back(P(x,i)); 28 } 29 } 30 for(int i=0;i<m;i++) { 31 std::sort(g[i].begin(),g[i].end()); 32 int mask=0; 33 for(P c:g[i]) { 34 mask|=1<<c.second; 35 f[mask]+=c.first; 36 f[mask^(1<<c.second)]-=c.first; 37 } 38 } 39 FWT(f,1<<n); 40 for(int i=1;i<=n;i++) ans[i]=1e18; 41 for(int i=1;i<1<<n;i++) { 42 int k=0; 43 for(int j=0;j<n;j++) if(i>>j&1) ++k; 44 ans[k]=std::min(ans[k],f[i]); 45 } 46 for(int k;q--;) { 47 scanf("%d",&k); 48 printf("%lld\n",ans[k]); 49 } 50 } 51 return 0; 52 }