【NOIP模拟8.7】

T1 Maximum

题意:给出一个序列a,求其中任意两项进行与/或/异或运算后的最大值
30分解法:暴力枚举两个数,复杂度 O(n2)
60分解法:注意到有 %30 的数据数字小于1024,可以用一个cnt数组记录出现过的值,然后从1到1024枚举,复杂度 O(10242)
100分解法:
先将60分特判掉,然后考虑到大数被选的几率大,假设出题人很懒,不想构造坑,将数据排序一下,我们从最大的数中选200个,从最小的数中选200个,暴力枚举答案, O(nlongn+q2002) ,然后就莫名其妙的A了。这就是正解你信吗
正解
每一种运算需要分开考虑

  • &:按位贪心,从高到低每一位枚举,如果所有数字第i位上1的个数>=2 ,
    则答案的第 i 位就可以是1,同时把第i位上不为1的数字删去,复杂度 O(20n)
  • | :从高位到低,如果某一位是0,需要判断能否为这个数配上一个1,类似于前缀和地预处理一下即可复杂度 O(20n)
  • ^ : 建一棵字典树,每一个节点只有0/1两个分支,所以是个二叉树,将每一个数字依次插入字典树中,每插入一个数之前统计其他数和这个数异或值最大是多少,同样贪心地从高到低位查询每一位是否有不同的值即可,复杂度 O(20n)

PS:可以用FWT加速(WTF)

//by sdfzchy
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int M=1048576;
const int N=100010;
int n,c;
int num[N],ans,a[M],base[21],u[N],maxn;
bool used[N],tr[(M<<1)+5];

void solveand(int pos)
{
    if(pos==0) return;
    int tot=0,cnt=0;
    for(int i=1;i<=n;i++)
    if(!used[i])
    {
        if(num[i]&base[pos-1]) ++cnt;
        else u[++tot]=i,used[i]=1;
    }
    if(cnt>=2){ solveand(pos-1) ; ans+=(1ll<<(pos-1)) ; }
    else { for(int i=1;i<=tot;i++) used[u[i]]=0 ; solveand(pos-1); }
}

void add(int rt,int len,int x)
{
    tr[rt]=1;
    if(len==-1) return ;
    if(x&base[len]) add(rt<<1|1,len-1,x);
    else add(rt<<1,len-1,x);
}

int Query(int rt,int len,int x)
{
    if(len==-1) return 0;
    if(x&base[len])
    {
        if(tr[rt<<1]) return Query(rt<<1,len-1,x)+base[len];
        return Query(rt<<1|1,len-1,x);  
    }
    if(tr[rt<<1|1]) return Query(rt<<1|1,len-1,x)+base[len];
    return Query(rt<<1,len-1,x);
}

void solvexor(int maxn)
{
    memset(tr,0,sizeof(tr));
    add(1,19,num[1]);
    for(int i=2;i<=n;i++)
    {
        ans=max(ans,Query(1,19,num[i]));
        add(1,19,num[i]);
    }
}

void solveor(int maxn)
{
    memset(a,0,sizeof(a));
    for(int i=1;i<=n;i++) a[num[i]]=1;
    for(int i=0;i<20;i++)
        for(int j=1;j<M;j++)
            if(a[j]&&(j&base[i])) a[j^base[i]]=1;
    int ret,tmp;
    for(int i=1;i<=n;i++)
    {
        ret=tmp=0;
        for(int j=19;j>=0;j--)
            if(num[i]&base[j]) ret|=base[j];
            else if(a[tmp|base[j]]) {ret|=base[j];tmp|=base[j];}
        ans=max(ans,ret);   
    }
}

int main()
{
    freopen("maximum.in","r",stdin);
    freopen("maximum.out","w",stdout);
    int T;
    scanf("%d",&T);
    base[0]=1;
    for(int i=1;i<=20;i++) base[i]=base[i-1]*2;
    while(T--)
    {
        maxn=ans=0;
        scanf("%d%d",&n,&c);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&num[i]);
            maxn=max(maxn,num[i]);
        }
        maxn=log(maxn)/log(2)+1;
        //cout<<maxn<<endl;
        if(c==1)
        {
            solveand(maxn);
            memset(used,0,sizeof(used));
        }
        else if(c==2)
        {
            solvexor(maxn);
        }
        else solveor(maxn);
        printf("%d\n",ans); 
    }
    return 0;
}

T2 bridge

题意:给定若干坐标轴上的点的坐标和移动方向,每秒移动一个单位长度,两个点相遇瞬间改变方向,求某时间之后某个点的位置,每组数据包括多次查询;
题解:

  • 10分:大力模拟
  • 我也不知道多少分:我们发现了两个规律,第一,只考虑点的位置,两个点相遇之后,可以看成运动方向没有变,只是两个点的编号互换,相当于他们互穿而过;第二,每一个点在整个序列中的相对排名不会变,直观的解释是一个点只会在相邻的两个点之间碰来碰去,那么发现这两个规律之后,我们可以每次直接计算出一个点可以到达的最终位置,排序之后找出第k大即可
  • 接近满分:std的算法,结合上面的规律,考虑这样一个性质,将所有初始方向相同的点归为一类,那么只考虑点的位置,这些点最终的相对位置其实是不变的,所有向右走的终点都是 pos[i]+t ,向左走的终点是 pos[i]t ,那么问题可以转化成在两个排好序的数组中,找出归并后第k大的元素。考虑二分答案,我们二分一个初始位置,每次check一下a数组和b数组的小于这个值的总数是否<=k,在每一个数组里再次二分查找第一个大于二分出答案的位置即可,注意相对位置的变化,复杂度 O(nlogn+qlog2n)
  • 100分:考虑解决两个排序好的数组求归并后第k大的更优解法,每次二分两个数组的中间值,把较大的中间值的后半段区间砍去,直到二分出答案,
  • 复杂度 O(nlogn+qlogn)
    100分做法其实是一个经典模型,即两个排序好的数组求归并好后的第k大,附讲解,戳一戳
//by sdfzchy
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n;
const int N=2e5+5;
const long long inf=(1ll<<40);
long long a[N],b[N];
int id[N],q;
struct node
{
    int pos,d,id;
    bool operator <(const node& a)const
    {
        return pos<a.pos;
    }
}d[N];

inline int binary(long long x,long long *c)
{
    if(!c[0]) return 0;
    if(x<=c[1]) return 0;
    if(x>c[c[0]]) return c[0];
    int mid,L=1,R=c[0],xx=x;
    while(L<R)
    {
        mid=(L+R+1)>>1;
        if(c[mid]<xx) L=mid;
        else R=mid-1;
    }
    return L;
}

void work()
{
    int x;
    long long minl=inf,maxn=-inf,t;
    scanf("%d%lld",&x,&t);
    x=id[x];x++;
    if(a[0]) minl=min(minl,a[1]-t),maxn=max(maxn,a[a[0]]-t);
    if(b[0]) minl=min(minl,b[1]+t),maxn=max(maxn,b[b[0]]+t);
    long long l=minl,r=maxn,mid;
//  cout<<l<<" "<<r<<endl;
    while(l<r)
    {
        mid=(l+r+1)>>1;
        if(binary(mid+t,a)+binary(mid-t,b)<x) l=mid;
        else r=mid-1;
    }
    printf("%lld\n",l);
}

int main()
{
    freopen("bridge.in","r",stdin);
    freopen("bridge.ans","w",stdout);
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&d[i].pos);
    for(int i=0;i<n;i++) scanf("%d",&d[i].d),d[i].id=i;
    sort(d,d+n);
    for(int i=0;i<n;i++) id[d[i].id]=i;
    for(int i=0;i<n;i++)
        if(d[i].d==0) a[++a[0]]=d[i].pos;
        else b[++b[0]]=d[i].pos;
    sort(a+1,a+a[0]+1);
    sort(b+1,b+b[0]+1);
    scanf("%d",&q);
    while(q--)  work();
    return 0;   
}

T3 Group

题意:对一个序列分组,使得每一组数据的极差的总和不超过K,求分组的方案数
分析:用 F[I][J][k] 表示前i个点,还有j组没有确定最大值,一共极差和已经是k的情况一共有多少种,那么最终的答案就是 F[N][0][K] K<=kmaxn 的和
对于每个点有四种情况,一种是新建一组不加,一种是新建一组等待最大值,一种是加入一组等待最大值,一种是加入一组不加。注意,要处理加入一组的最大值问题,本题输入顺序需要排序。
然后就是,对于代价,用差分的方式,每次加上两个点的差乘以j,因为只有没确定最大值的点才需要继续加,每确定一个点,它修改的状态的j就减一,保证了该状态的该组不再被加。

代码稍后补

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值