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 }
View Code

 

posted on 2019-08-16 21:16  唯我心  阅读(135)  评论(0编辑  收藏  举报