听说 WC2021 的 T2 和这题解法类似,于是就来做一发。
题意简述
初始有 \(k\) 个生物,编号分别为 \(1,2,\ldots,k\) ,每个生物有 \(n\) 个属性,第 \(i\) 个生物的第 \(j\) 个属性为 \(a_{i,j}\) 。
现在有 \(q\) 次操作,形式如下:
1 x y
表示将第 \(x\) 号生物的 \(n\) 个属性与第 \(y\) 号生物的 \(n\) 个属性分别取 \(\max\) ,作为一个新生物的 \(n\) 个属性2 x y
表示将第 \(x\) 号生物的 \(n\) 个属性与第 \(y\) 号生物的 \(n\) 个属性分别取 \(\min\) ,作为一个新生物的 \(n\) 个属性3 x y
表示询问第 \(x\) 号生物的第 \(y\) 个属性值是多少
新生物的编号依次顺延,即第 \(t\) 个 1/2
操作得到的新生物编号为 \(t+k\) 。
你需要对每一个 3 操作回答询问。
\(1\le n,q\le 10^5,1\le k\le 12,1\le a_{i,j}\le 10^9\) 。
算法分析
如果直接暴力做复杂度是 \(\mathcal O(nq)\) 的,无法接受。
本题 \(k\) 很小,是本题的突破点。不难发现对于每一种属性,我们最后的答案一定来自于初始的 \(k\) 个生物的该属性的值,因此考虑枚举答案。 \(\min,\max\) 本身代表一种偏序关系,与其具体值无关,所有可能的情况共有 \(2^k\) 个。这说明 \(n\) 种属性中最多有 \(2^k\) 个本质不同的属性,考虑维护这 \(2^k\) 种状态。
我们定义一个状态 \(S\) 如下:若 \(S\) 的第 \(i\) 位为 \(1\) ,表示当前状态下 \(a_i\ge\ 对应的答案\),否则 \(a_i<\ 对应的答案\),此时当前节点的真实答案是否 \(\ge\) 当前状态对应的答案。
那么我们不难发现 \(1,2\) 操作分别对应取 \(\mathrm{or}\) 和取 \(\mathrm{and}\) 。初始条件对于生物 \(i\) ,\(f_i(S)=1\ 当且仅当\ S_i=1\) 。
对于一个查询,我们从大到小枚举所有的可能答案,不难求出这个答案对应的状态 \(S\) 。根据上述定义,当我们枚举到第一个状态对应值为 \(1\) 的答案时,此时答案即为真实答案。
发现可以用 bitset
优化,最终时间复杂度为 \(\mathcal O\left(\frac{q2^k+qk^2}{\omega}\right)\) 。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define maxm 2000005
#define inf 0x3f3f3f3f
#define LL long long
#define mod 1000000007
#define local
void file(string s){freopen((s+".in").c_str(),"r",stdin);freopen((s+".out").c_str(),"w",stdout);}
template <typename Tp> void read(Tp &x){
int fh=1;char c=getchar();x=0;
while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
int n,m,mm,q;
int a[13][maxn];
bitset<4100>f[maxn];
int tmp[20],ttt;
void solve(int x,int y){
ttt=0;
for(int i=1;i<=m;++i)tmp[++ttt]=a[i][y];
sort(tmp+1,tmp+ttt+1);
ttt=unique(tmp+1,tmp+ttt+1)-tmp-1;// 这里做了离散化,在最优化问题中(即本题)不一定有必要
int S; // 但在计数问题中(WC2021T2)必须离散化
for(int i=ttt;i;--i){
int xx=tmp[i];S=0;// 枚举答案xx,求出对应状态S
for(int j=1;j<=m;++j){
if(a[j][y]>=xx)S|=(1<<(j-1));
}
if(f[x][S]){//第一个值为1的状态对应答案即为真实答案
printf("%d\n",xx);
break;
}
}
}
signed main(){
read(n);read(m);read(q);
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
read(a[i][j]);
}
for(int S=0;S<(1<<m);++S){//初始状态求解
if((S>>(i-1))&1)f[i][S]=1;
else f[i][S]=0;
}
}
mm=m;
for(int i=1,op,x,y;i<=q;++i){
read(op);read(x);read(y);
if(op==1){//操作1对应 or
++mm;
f[mm]=(f[x]|f[y]);
}
if(op==2){//操作2对应 and
++mm;
f[mm]=(f[x]&f[y]);
}
if(op==3){
solve(x,y);
}
}
return 0;
}