• Codeforces Round #716&#717 (Div. 2)


    716D

    题意

    给一个长为n的数列a。对于一个区间的元素,可以把它分为若干个子序列(可以不连续的分)。现在有q次询问,每次询问求一个区间l-r内最少要把区间分成多少个子序列,使得每个子序列出现最多次的元素不超过区间长度的一半(向上取整)。

    思路

    首先很容易想到,一个子序列只能有一个元素出现次数超过区间长度一半,所以只需要考虑子序列的众数就可以了。

    然后再考虑划分的问题。

    假设现在众数出现k次,那么它需要k-1个不等与它的数分配在一起,才能使得这个子序列合法。

    对于某个区间,可以把里面的元素分成两类,众数和非众数。假设众数出现k次,那么如果非众数的数量大于等于k-1,则序列合法。否则需要划分。

    因为出现k次的众数需要k-1个非众数,难么把k划分为a+b,则需要a-1+b-1个非众数。即k-2个非众数。以此类推,可以得出规律,把区间划分为n个合法子序列,最少需要k-n个非众数。

    又由非众数的数量为len-k,可得k-n=len-k,即n=2k-len。所以问题就转化成了无修改多次询问区间众数的出现次数。

    区间众数问题,有很多解法,本题大概有5种解法。

    1.首先很容易想到用莫队算法。不过莫队是离线的,要求强制在线的题目无法使用。本题不强制在线,故可以使用。时间复杂度O(nsqrt(n))

    2.除此之外,应当注意到,只有超过区间长度一半的众数才会对答案有贡献,所以如果有这样的众数,随机在区间内选一个数,选到该众数的可能性大于1/2

    所以考虑随机化。每次在区间内随便选m个数,那么这些数都不是众数的概率为1/2^m。所以取一个合适的m,就可以大概率在m个数中选到众数。

    统计出现次数可以用一个技巧。开nvector,存储1-n每个数字出现的位置,然后用二分查找区间lrvector中的位置,upper_bound(r)-lower_bound(l)就是出现次数,然后每次取最大值,最后得到答案。假设取m个随机数,总体复杂度为O(qmlog(n))

    3.还可以用线段树解。不妨称出现次数超过区间长度的数为超众数。虽然众数问题不能分治,但是超众数可以分治解决,因为不存在某个不是左右区间超众数的数成为大区间的超众数,所以合并只需要考虑左右区间的超众数。

    线段树维护左右区间合并时只需要考虑超众数相等的值,以及左右区间一个存在一个不存在的情况,因为左右区间超众数值不相等则一定不会使得大区间有超众数。

    如果左右区间一个有超众数一个不存在,那么用二分的方法统计存在的那个超众数在大区间内的出现次数,就可以保持区间的性质,使器可以向上合并。

    查询时只需要每次二分查找每个在l-r范围内的子区间内的超众数在l-r区间内的出现次数即可。总体复杂度O(qlog^2(n))

    4.数列分块。可以参考clj大佬的《区间众数解题报告》。可以O(nsqrt(n))在线解决。但是代码比较复杂,而且常数比较大,用来解决本题优先级不是很高。

    5.可持久化线段树。对区间内数的个数建树,某节点存储有多少数据出现在该节点代表的区间内。(如某节点代表1-2区间,则存储有多少[1,2]之间的数,具体可以参考可持久化线段树相关教程)。建树完成之后,对于每次询问l,r,利用前缀和性质,查询版本r到版本l-1之间是否有出现次数超过(r-l+2)/2的数,有则返回出现次数。然后计算答案,总体复杂度O(nlog(n)),速度最快但是空间复杂度较大。

    代码

    莫队:

    #include<bits/stdc++.h>
    using namespace std;
     
    const int MAX=3e5+5;
     
    int bl[MAX],a[MAX],cnt[MAX],num[MAX],res[MAX],cur;
     
    struct Query
    {
        int l,r,id;
        bool operator <(const Query A)
        {
            if(bl[l]==bl[A.l])return bl[r]<bl[A.r];
            return bl[l]<bl[A.l];
        }
    }q[MAX];
     
    void del(int x)
    {
        num[cnt[a[x]]]--;
        num[--cnt[a[x]]]++;
        while(!num[cur])cur--;
    }
     
    void add(int x)
    {
        num[cnt[a[x]]]--;
        num[++cnt[a[x]]]++;
        while(num[cur+1])cur++;
    }
     
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        int root=sqrt(n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),bl[i]=(i-1)/root+1;
        for(int i=0;i<m;i++)
            scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
        sort(q,q+m);
        cur=0,num[0]=1;
        int l=1,r=0;
        for(int i=0;i<m;i++)
        {
            while(l>q[i].l)add(--l);
            while(r<q[i].r)add(++r);
            while(l<q[i].l)del(l++);
            while(r>q[i].r)del(r--);
            res[q[i].id]=cur*2-q[i].r+q[i].l-1;
        }
        for(int i=0;i<m;i++)
            printf("%d
    ",max(res[i],1));
    }
    

    随机化:这里需要用mt19937rand范围太小,会影响随机化的效果。

    #include<bits/stdc++.h>
    using namespace std;
     
    const int MAX=3e5+5;
     
    int a[MAX];
    vector<int>pos[MAX];
     
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),pos[a[i]].push_back(i);
        mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
        for(int i=0;i<m;i++)
        {
            int T=30,l,r,maxx=-1;
            scanf("%d%d",&l,&r);
            uniform_int_distribution<int>rn(l,r);
            while(T--)
            {
                int cur=a[rn(rng)];
                maxx=max(maxx,upper_bound(pos[cur].begin(),pos[cur].end(),r)-lower_bound(pos[cur].begin(),pos[cur].end(),l));
            }
            printf("%d
    ",max(1,2*maxx-r+l-1));
        }
    }
    

    线段树:

    #include<bits/stdc++.h>
    using namespace std;
     
    const int MAX=3e5+5;
     
    struct data
    {
        int maxx;
    }tree[MAX<<2];
    int a[MAX];
    vector<int>pos[MAX];
     
    int cnt(int l,int r,int key)
    {
        return upper_bound(pos[key].begin(),pos[key].end(),r)-lower_bound(pos[key].begin(),pos[key].end(),l);
    }
     
    void pushup(int rt,int l,int r)
    {
        int ls=rt<<1,rs=rt<<1|1;
        if(tree[ls].maxx==tree[rs].maxx)tree[rt].maxx=tree[ls].maxx;
        else tree[rt].maxx=(cnt(l,r,tree[ls].maxx)>cnt(l,r,tree[rs].maxx))?tree[ls].maxx:tree[rs].maxx;
    }
     
    void build(int l,int r,int rt)
    {
        if(l==r)
        {
            tree[rt].maxx=a[l];
            return ;
        }
        int m=(l+r)>>1;
        build(l,m,rt<<1);
        build(m+1,r,rt<<1|1);
        pushup(rt,l,r);
    }
     
    int query(int L,int R,int l,int r,int rt)
    {
        if(L<=l&&r<=R)
        {
            return cnt(L,R,tree[rt].maxx);
        }
        int m=(l+r)>>1,ans=-1;
        if(L<=m)
            ans=max(ans,query(L,R,l,m,rt<<1));
        if(R>m)
            ans=max(ans,query(L,R,m+1,r,rt<<1|1));
        return ans;
    }
     
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),pos[a[i]].push_back(i);
        build(1,n,1);
        while(m--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            int maxx=query(l,r,1,n,1);
            printf("%d
    ",max(1,maxx*2-r+l-1));
        }
    }
    

    可持久化线段树:

    #include<bits/stdc++.h>
    using namespace std;
    const int MAX=3e5+5;
     
    struct Node
    {
        int cnt,ls,rs;
    } tree[MAX<<5];
    int rt[MAX],tot=0;
     
    int build(int l,int r)
    {
        int cur=++tot;
        if(l==r)return cur;
        int mid=(l+r)>>1;
        tree[cur].ls=build(l,mid);
        tree[cur].rs=build(mid+1,r);
        return cur;
    }
     
    int update(int l,int r,int pre,int pos)
    {
        int cur=++tot;
        tree[cur].ls=tree[pre].ls;
        tree[cur].rs=tree[pre].rs;
        tree[cur].cnt=tree[pre].cnt+1;
        if(l==r)return cur;
        int mid=(l+r)>>1;
        if(pos<=mid)
            tree[cur].ls=update(l,mid,tree[pre].ls,pos);
        else
            tree[cur].rs=update(mid+1,r,tree[pre].rs,pos);
        return cur;
    }
     
    int query(int x,int y,int l,int r,int k)
    {
        if(l==r)
            if(tree[y].cnt-tree[x].cnt>k)return tree[y].cnt-tree[x].cnt;
        int mid=(l+r)>>1;
        if(tree[tree[y].ls].cnt-tree[tree[x].ls].cnt>k)return query(tree[x].ls,tree[y].ls,l,mid,k);
        if(tree[tree[y].rs].cnt-tree[tree[x].rs].cnt>k)return query(tree[x].rs,tree[y].rs,mid+1,r,k);
        return 0;
    }
     
    int main()
    {
        int n,m,x;
        scanf("%d%d",&n,&m);
        rt[0]=build(1,n);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&x);
            rt[i]=update(1,n,rt[i-1],x);
        }
        while(m--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            int maxx=query(rt[l-1],rt[r],1,n,(r-l+2)/2);
            printf("%d
    ",max(1,maxx*2-r+l-1));
        }
    }
    

    717C

    题意

    n个数,要求删除若干个数,使得这些数无法分为两个和相等的集合。求删除哪些数。

    思路

    显然可以想到,如果n个数的和sum为奇数,那么一定不存在两个集合和相等。如果sum为偶数,那么某一集合的和一定为sum/2

    如果sum为偶数,那么一定可以删除某一个奇数,使得sum为奇数,自然满足条件。

    如果不存在某个数为奇数,那么可以把每个数除2,直到其中某一个数变为奇数。因为如果n个数不存在某种划分方式使得两集合和相等,那么将这些数都乘2,也不会存在某种划分方式使得两集合和相等,因为数乘2后是一一对应的。

    还有一种情况,即sum为偶数,但是并不存在某种划分方式(很容易可以想到这种情况的例子)。所以还需要判断是否存在划分方式。

    可以等价为是否存在某个子集,使得子集和为sum/2,可以抽象成背包问题。

    代码

    学习了一下用bitset写背包,很高效,但是只能用在判断方案是否存在的问题。

    #include<bits/stdc++.h>
    using namespace std;
     
    const int MAX=1e2+5;
    int a[MAX];
     
    int test(int sum,int n)
    {
        if(sum&1) return -1;
        bitset<MAX*2000>dp;
        dp[0]=1;//dp[i]表示是否存在重量为i的方案。
        int minn=MAX,res=-1;
        for(int i=0;i<n;i++)
        {
            int k=0;
            while(!(a[i]&(1<<k)))k++;
            if(k<minn){minn=k;res=i+1;}
            dp|=(dp<<a[i]);//转移,将前一种状态左移,然后按位或
        }
        if(dp[sum/2])return res;
        else return -1;
    }
     
    int main()
    {
        int n,sum=0,res=-1;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        res=test(sum,n);
        if(res>0) printf("1
    %d
    ",res);
        else printf("0
    ");
    }
    

    717D

    题意

    给一个数组,每次查询一个区间,要求把该区间分成若干连续子区间,使得每个子区间内的数互质。求最少需要分成多少连续子区间。

    思路

    对于每个询问l-r,从最左边从左往右贪心的每次选最大的互质区间。但是多次询问肯定会超时。所以需要解决两个问题

    1. 计算互质区间问题,如果每个询问都从头开始计算两两互质,那么肯定会超时。所以对每个数a[i],预处理出该数所能跳转到的下一个数a[j],使得[i,j-1]区间内所有数互质。从后往前扫,对每个数分解质因数,记录每个质因数最后出现的位置p,并且把a[i]的跳转位置设为它所有质因数的最后位置的最小值。这样就处理出来了跳转表。
    2. 快速查询问题。一个区间内可能有很多跳转,如果按顺序去跳转那么可能会超时。很容易想到,上一步处理出来的结果符合倍增的特点。所以跑一遍倍增,每次询问就可以log(n)完成。

    代码

    #include<bits/stdc++.h>
    using namespace std;
     
    const int MAX=1e5+5;
    const int INF=0x3f3f3f3f;
    int last_place[MAX],a[MAX],dp[MAX][20];
     
    int main()
    {
        int n,q;
        scanf("%d%d",&n,&q);
        for(int i=0; i<n; i++)
            scanf("%d",&a[i]);
        int maxx=n;
        memset(last_place,INF,sizeof last_place);
        for(int i=n-1; i>=0; i--)
        {
            for(int j=2; j*j<=a[i]; j++)
            {
                if(a[i]%j==0)
                {
                    maxx=min(maxx,last_place[j]);
                    last_place[j]=i;
                    while(a[i]%j==0)a[i]/=j;
                }
            }
            if(a[i]!=1)
            {
                maxx=min(maxx,last_place[a[i]]);
                last_place[a[i]]=i;
            }
            dp[i][0]=maxx;
        }
        for(int i=0;i<20;i++)dp[n][i]=n;
        for(int i=n-1; i>=0; i--)
            for(int j=1; j<20; j++)
                dp[i][j]=i+(1<<j)>=n?n:dp[dp[i][j-1]][j-1];
        while(q--)
        {
            int l,r,res=0;
            scanf("%d%d",&l,&r);
            l--;
            r--;
            for(int i=19; i>=0; i--)
                if(dp[l][i]<=r)
                {
                    l=dp[l][i];
                    res+=(1<<i);
                }
            printf("%d
    ",res+1);
        }
    }
    
  • 相关阅读:
    详解单例模式
    Spring整合Mybatis案例,献给初学的朋友
    Java反射学习总结
    IDEA JSP项目构建及学习心得
    SQL Server数据库表锁定原理以及如何解除表的锁定
    MySQL锁定机制简介
    Cassandra简介
    Linux机器上实现程序自动部署以及更新
    服务器时间同步平台化
    内存查看平台化
  • 原文地址:https://www.cnblogs.com/cryingrain/p/14849037.html
Copyright © 2020-2023  润新知