6.19联考题解

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

单独考虑每个数对答案的贡献,发现不管他是否合并,他都一直在这些数里面,因此剩余 m 个数的时候他被选中的概率就是 2 m ,因此 n 1 轮后他贡献的总概率就是 i = 2 n 2 i
所以 a n s = a i i = 2 n 2 i i = 2 n i ( i 1 ) 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个操作,每个操作形如将 [ L i , R i ] 内的数改成这个区间的最大值,q次修改/询问,每次更改原始数组中的一个数,或者询问如果执行 [ l , r ] 内的操作,数组第 k 个位置的值是多少

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

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

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

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

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

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

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

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 ( k l o g n ) 那个东西是怎么做的,只会 O ( n k )

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

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

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

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

一共拓展 k 1 次,复杂度 O ( n k )

猜你喜欢

转载自blog.csdn.net/l_0_forever_lf/article/details/80741002