6.19联考题解

版权声明:...............转载说一声并注明出处qaq............... https://blog.csdn.net/L_0_Forever_LF/article/details/80741002

A:
n个数,每次随机两个数合并,贡献是这两个数的和,求总贡献的期望乘i=2ni(i1)2

单独考虑每个数对答案的贡献,发现不管他是否合并,他都一直在这些数里面,因此剩余m个数的时候他被选中的概率就是2m,因此n1轮后他贡献的总概率就是i=2n2i
所以ans=aii=2n2ii=2ni(i1)2

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int mod = 1e9+7;
const int maxn = 210000;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}

int pw(int x,int k)
{
    int re=1;
    for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
        re=(ll)re*x%mod;
    return re;
}
int inv(int x){ return pw(x,mod-2); }

int s[maxn],invs[maxn],invi[maxn];
void pre()
{
    s[0]=1; for(int i=1;i<maxn;i++) s[i]=(ll)s[i-1]*i%mod;
    invs[maxn-1]=inv(s[maxn-1]);
    for(int i=maxn-2;i>=0;i--) invs[i]=(ll)invs[i+1]*(i+1)%mod;
    for(int i=1;i<maxn;i++) invi[i]=(ll)s[i-1]*invs[i]%mod;
}

int n;

int main()
{
    freopen("huffman.in","r",stdin);
    freopen("huffman.out","w",stdout);

    pre();
    scanf("%d",&n);
    int ans1=0,ans2=0,ans3=1;
    for(int i=1;i<=n;i++)
    {
        int x; scanf("%d",&x); add(ans1,x);
        if(i>1)
        {
            add(ans2,invi[i]);
            ans3=(ll)ans3*i%mod*(i-1)%mod*invi[2]%mod;
        }
    }
    ans2=(ll)ans2*2%mod;
    printf("%lld\n",(ll)ans1*ans2%mod*ans3%mod);

    return 0;
}

B:
n个数的序列,m个操作,每个操作形如将[Li,Ri]内的数改成这个区间的最大值,q次修改/询问,每次更改原始数组中的一个数,或者询问如果执行[l,r]内的操作,数组第k个位置的值是多少

执行若干次操作后,数组中位置i的值会取到原始数组里一个区间[li,ri]内的最大值,且一定有li<=li+1,ri<=ri+1,我们可以看成每个位置i有个二元组(li,ri),我们只要求出执行[l,r]内的操作后位置k的二元组(lk,rk)就能直接在维护原始序列的线段树上找解了

一开始li=ri=i,考虑一次操作的影响,对[L,R]操作,会将[L,R]内的lilL取min,rirR取max
我们只需要考虑怎么求这些操作后的lkrk同理

问题变成了序列一开始每个位置有一个值xi=i,每个操作是把[L,R]内的值和xL取min,求执行[l,r]内的操作后xk的值是多少

不妨二分xk,那么可以将序列看成一个01序列,一开始左边mid个数是1,每次操作如果[L,R]内有1,就会把[L,R]全部变成1
因为是01序列且一定是左边一段1,剩下全是0,我们可以直接用一个数x代表这个序列当前的状态:左边x个数是1。每次操作就是把>=L,<Rx改成R

那么一次询问二分后就变成了一个数字x,我们要对他执行[l,r]内的所有操作,考虑到数字是很好维护的,我们不妨将所有询问放在一起二分(注意这不是也不能是整体二分,因为每个询问的二分区间都是不同的,我们把他们放在一起二分只是因为数字可以一起维护),正着扫一遍所有操作,对于一个操作区间是[l,r]的询问,我们在第l次操作加入,第r+1次操作取出他的值,更改二分的上下界
维护的话,可以直接把这些数字放进一个multiset里面维护,每次操作就把[L,R)内的值全部取出来,用并查集合并到一起改成R再放进去,因为总合并次数是O(q)的所以复杂度是对的

那么我们就可以做到O(mlognlogq)
比赛的时候我觉得这样就能过了(对常数太自信….)然后T掉了

怎么优化到一个log呢
考虑倒着做,每个询问开始的数字x=k,求li我们就不断的将(L,R]中的xL取min,ri同理
就可以去掉二分啦
复杂度O(mlogq+qlogn)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define pb push_back
#define SZ(x) (int)x.size()
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 110000;

int n,m,q;
int ai[maxn];
int op[maxn][2];
struct Qu
{
    int l,r,k;
    int nows,ansl,ansr;
}qq[maxn];

namespace Task1
{
    vector<int>Vin[maxn],Vout[maxn];

    int fa[maxn],s[maxn];
    int findfa(const int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);}
    struct node
    {
        int x,i;
        friend inline bool operator <(const node x,const node y){return x.x<y.x;}
    };
    multiset<node>S;
    multiset<node>::iterator it,it2;
    int t[maxn],tp;

    void main()
    {
        for(int i=1;i<=n;i++) Vin[i].clear(),Vout[i].clear();
        for(int i=1;i<=q;i++) if(qq[i].k)
            Vin[qq[i].r].pb(i),Vout[qq[i].l-1].pb(i);

        for(int i=1;i<=q;i++) s[i]=qq[i].k,fa[i]=i;

        S.clear();
        for(int i=m;i>=0;i--)
        {
            for(int j=0;j<SZ(Vout[i]);j++)
            {
                int ii=Vout[i][j];
                qq[ii].nows=s[findfa(ii)];
            }
            for(int j=0;j<SZ(Vin[i]);j++)
            {
                int ii=Vin[i][j];
                node temp=(node){s[ii],ii};
                S.insert(temp);
            }

            node temp=(node){op[i][0],0};
            it=S.lower_bound(temp);
            while(it!=S.end()&&(*it).x<op[i][1])
            {
                it2=it; it2++;
                t[++tp]=(*it).i;
                S.erase(it);
                it=it2;
            }
            if(tp)
            {
                int newi=t[tp]; s[newi]=op[i][1];
                while(tp) fa[t[tp--]]=newi;
                S.insert((node){s[newi],newi});
            }
        }

        for(int i=1;i<=q;i++) if(qq[i].k) qq[i].ansr=qq[i].nows;


        for(int i=1;i<=q;i++) s[i]=qq[i].k,fa[i]=i;

        S.clear();
        for(int i=m;i>=0;i--)
        {
            for(int j=0;j<SZ(Vout[i]);j++)
            {
                int ii=Vout[i][j];
                qq[ii].nows=s[findfa(ii)];
            }
            for(int j=0;j<SZ(Vin[i]);j++)
            {
                int ii=Vin[i][j];
                node temp=(node){s[ii],ii};
                S.insert(temp);
            }

            node temp=(node){op[i][1],0};
            it=S.upper_bound(temp);
            int nex=(it==S.begin())?0:1;
            if(nex) it2=it,it2--;
            while(nex&&(*it2).x>=op[i][0])
            {
                it=it2; nex=(it==S.begin())?0:1;
                if(nex) it2--;
                t[++tp]=(*it).i;
                S.erase(it);
            }
            if(tp)
            {
                int newi=t[tp]; s[newi]=op[i][0];
                while(tp) fa[t[tp--]]=newi;
                S.insert((node){s[newi],newi});
            }
        }


        for(int i=1;i<=q;i++) if(qq[i].k) qq[i].ansl=qq[i].nows;
    }
}

struct Segment
{
    int seg[maxn<<2];
    int loc,c,lx,rx;
    void build(const int x,const int l,const int r)
    {
        if(l==r) { seg[x]=ai[l];return; }
        int mid=(l+r)>>1;
        build(lc,l,mid),build(rc,mid+1,r);
        seg[x]=max(seg[lc],seg[rc]);
    }
    void upd(const int x,const int l,const int r)
    {
        if(l==r) { seg[x]=c;return; }
        int mid=(l+r)>>1;
        loc<=mid?upd(lc,l,mid):upd(rc,mid+1,r);
        seg[x]=max(seg[lc],seg[rc]);
    }
    int query(const int x,const int l,const int r)
    {
        if(rx<l||r<lx) return 0;
        if(lx<=l&&r<=rx) return seg[x];
        int mid=(l+r)>>1;
        return max(query(lc,l,mid),query(rc,mid+1,r));
    }
}seg;

int main()
{
    freopen("segment.in","r",stdin);
    freopen("segment.out","w",stdout);

    read(n),read(m),read(q);
    for(int i=1;i<=n;i++) read(ai[i]);
    for(int i=1;i<=m;i++) read(op[i][0]),read(op[i][1]);

    for(int i=1;i<=q;i++)
    {
        int type; read(type);
        read(qq[i].l),read(qq[i].r);
        if(type==2) read(qq[i].k);
        else qq[i].k=0;
    }

    Task1::main();

    seg.build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        if(qq[i].k) seg.lx=qq[i].ansl,seg.rx=qq[i].ansr,printf("%d\n",seg.query(1,1,n));
        else seg.loc=qq[i].l,seg.c=qq[i].r,seg.upd(1,1,n);
    }

    return 0;
}

C:
我看不懂题解….不知道O(klogn)那个东西是怎么做的,只会O(nk)

对于这类求k优解的问题一般考虑用A解决

一开始将A降序排序,最优解肯定是iAi
对于一个解,其一定形如iApi
一开始令pi=i
我们定义一个旋转操作rot(l,r),代表令pl=prpl~pr1原来的值全部右移一位,比如如果我们操作的区间内的pi是1,2,3,4,5,操作完就是5,1,2,3,4
pi=i时,rot(l,r)后答案的增量rot+(l,r)=i=l+1rAlAi
只要p是递增的,增量一定为正且有rot+(l,r)<=rot+(l,r+1)

同时可以发现任意一个排列p一定可以由起始序列pi=i经过若干次l单调升的旋转rot(l,r)得到,且只要l单调增,我们每次旋转都会得到一个和之前不同的排列

然后就可以跑A,对当前序列可行的每个l设置一个当前拓展的rO(n)计算增量rot+(l,r),序列维护所有l增量的最小值,再维护序列增量最小值,每次旋转后拓展一个状态设置新状态旋转的l的下限,更改旧状态的r并重新计算增量

一共拓展k1次,复杂度O(nk)

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页