1288D. Minimax Problem (二分+状态压缩)
题意&&思路
题意: 给出 n 个长度为 m 的序列(1 ≤ n ≤ 3*1e5, 1≤ m ≤8 ),对于序列中的每一个元素 a,(0≤a≤1e9) 现在你要求出,对于所有任意两行序列为一组(或则自己本身成为一组),进行(两个序列的同一个位置取最大值放到新序列中)操作形成新的序列,再从新的序列中选取最小值作为ans,最后要对所有可能组成的ans中输出最大的ans. 思路: 说实话没有想到用二分加二进制压缩表示来求解。二分倒是可以想到,毕竟正向 把两两所有枚举都是 O( n^2 )了,所以就容易想着反向去求解答案。 题解给的是:二分去选取一个数(作为假设的最终答案),把 序列中比这个数小的 记为0,大于等于这个数记为1. 最后就可以得到一个(0~2^m-1)范围类的二进制数。 也就是说本来我们需要 O( n^2 )去匹配的,现在状态压缩用二进制去等同表示后只需要O( m^2 )去验证,而m的范围为 1≤ m ≤8,所以就大大降低了复杂度。 而验证该数 X 是答案的规则即是,是否存在两个二进制数,最后 或 的结果为 全1 .(因为由于取得是最大序列的最小值,如果两个序列或为1,那么这两个序列中必然能够选出一个大于等于X 的序列) 例如:下面两个序列红色表示对应位置的值更大(假设选取的 x 能作为答案)。 a b c d e -> 1 0 1 0 1 A B C D E -> 0 1 1 1 0
Code:
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int inf=0x3f3f3f3f; const int MAXN=3e5+10; int n,m,a[MAXN][10],mark[300],pos1,pos2,ans1,ans2; //bitmasks bool check(int x){ pos1 = 0,pos2 = 0; memset(mark,0,sizeof(mark)); for(int i=1;i<=n;i++){ int tot = 0; for(int j=0;j<m;j++){ if(x<=a[i][j]) tot += (1<<j); } mark[tot] = i; } for(int i=1;i<= (1<<m)-1;i++){ for(int j=i; j<= (1<<m)-1;j++){ if((i|j)== (1<<m)-1&&mark[i]&&mark[j]) { pos1 = mark[i]; pos2 = mark[j]; } } } return pos1&&pos2; } //binary search int BS(int l,int r){ int mid; while(l<=r){ mid = (l+r)>>1; if(check(mid)) l = mid+1,ans1 = pos1,ans2 = pos2; else r = mid-1; } return l; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=0;j<m;j++) scanf("%d",&a[i][j]); int l=0,r=1e9; l = BS(l,r); printf("%d %d ",ans1,ans2); return 0; }
1288E. Messenger Simulator (树状数组)
题意&&思路
题意:定义初始序列为给定长度为N的序列 1,2,3...,N ,再给出一个长为M的序列(1≤N,M≤3e5) ,表示对应操作。模拟操作为对应值位置提到序列的最前面,比如原序列为1 2 3 4 5,此时给出操作3,即变为 3 1 2 4 5. 最后输出对应从 1到N 每个数在整个操作中的最小位置和最大位置 思路: 要统计一个数的之前有多少个数出现,一般的想法便是遍历从1到该数的位置。如果我们用一个vis[] 表示某个位置上是否有值,pos[value] 记录每个值的当前位置,那么只需要求从 1到pos[value]的前缀和为多少即可。所以此时使用树状数组来降低计算前缀和的复杂度到 logn . (类似操作可以参考一下:树状数组对逆序数的计算:冒泡排序的交换次数 (树状数组) )
Code:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> typedef long long ll; using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 3e5+5; const int maxm = 5e5+5; int t[maxn<<1];//树状数组要开两倍,由于N+M的范围 int minp[maxn]; int maxp[maxn]; int pos[maxn]; int N,M,K; int lowbit(int x){ return x&-x; } void add(int x,int k){ while(x<=N+M){ t[x] += k; x += lowbit(x); } } int ask(int x){ int ans = 0; while(x>0){ ans += t[x]; x -= lowbit(x); } return ans; } int main(){ cin>>N>>M; memset(t,0,sizeof(t)); for(int i=1;i<=N;i++) { pos[i]=i+M;//初始位置 minp[i] = maxp[i] = i; add(pos[i],1); } //动态查询 for(int i=1;i<=M;i++){ int temp; cin>>temp; minp[temp] = 1;//更新最小位置 maxp[temp] = max(maxp[temp],ask(pos[temp])); add(pos[temp],-1);//删除原位置 pos[temp] = M-i+1;//更新该数最新位置 add(M-i+1,1);//更新位置 } for(int i=1;i<=N;i++) maxp[i] = max(maxp[i],ask(pos[i]));//最后再更新一次 for(int i=1;i<=N;i++){ cout<<minp[i]<<" "<<maxp[i]<<endl; } }