Educational Codeforces Round 80 (Div. 2)

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

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\n",ans1,ans2);
    return 0;
}
View Code

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 . (类似操作可以参考一下:树状数组对逆序数的计算:冒泡排序的交换次数 (树状数组) )
View Code

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

 

posted @ 2020-01-27 13:27  Tianwell  阅读(154)  评论(0编辑  收藏  举报