bzoj 5040: 未来研究

题解

这题相比起历史研究,有一个十分重要的条件

Ai<Aj<Bi<=Bj

这句话什么意思呢?
就是说,任意两个操作,只有相离和内含两个关系
这启示我们,如果我们把最近一个包含关系连边的话,他是一棵树
比如说,三个询问(1,3)(1,2)(1,1)
我们可以这么连边(1,3)->(1,2)->(1,1)
这样的话就是一棵树了,是吧
然后我们就是在树上面考虑这个问题
其实就是树上每个点有个L,R
然后问我们这一段怎么搞
当然他有很多孩子
这种问题,很明显,是可以将孩子并到父亲那里去的
那怎么搞呢?
Dsu on tree!
时间复杂度和做法在上面都有了,我就不再赘述了
于是,就可以在 nlogn 的复杂度里面优秀地解决这个题了!
至于怎么建树,一开始想的是按长度排序,然后用一个线段树维护这个区间要连向谁
但后来采取了一个单调栈的方法
就是,我们按L为第一关键字,R为第二关键字,第一个升序,第二个降序
然后搞一搞就好了。。具体看代码。。

当然,其实如果你写线段树合并的话,我不知道可不可以啊。。反正没有Dsu on tree好写吧。。

最后这题要卡常。。我优化了好一会,才10s卡过。。

数据生成器(你可以先生成一堆一定是包含的,然后再再里面随机):

#include<bits/stdc++.h>
using namespace std;
int L[1000000],R[1000000];
int tot=0;
int n=550000,t=400000;
void solve (int l,int r)
{
    int op=rand()%10;
    if (op==0&&tot<t) L[++tot]=l,R[tot]=r;
    if (l==r) return ;
    int mid=(l+r)>>1;
    solve(l,mid);
    solve(mid+1,r);
}
int main()
{
    srand(time(0));
    solve(1,n);
    printf("%d %d\n",n,tot);
    for (int u=1;u<=n;u++) printf("%d ",rand()*rand()%10000+10000);
    printf("\n");
    for (int u=1;u<=tot;u++)
        printf("%d %d\n",L[u],R[u]);
}

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
typedef long long LL;
const int N=600005*2;
int n,q;
int a[N];
struct qq
{
    int l,r,id;
}s[N];
LL ans[N];
struct qt
{
    int x,y,last;
}e[N];int num,last[N];
bool cmp (qq a,qq b)    {return a.l==b.l?a.r>b.r:a.l<b.l;}
bool cmpi (qq a,qq b)   {return a.id<b.id;}
void init (int x,int y)
{
    num++;
    e[num].x=x;e[num].y=y;
    e[num].last=last[x];
    last[x]=num;
}
stack<int> S;
int son[N],tot[N];
void dfs1 (int x)
{
    tot[x]=(s[x].r-s[x].l+1);
    for (int u=last[x];u!=-1;u=e[u].last)
    {
        int y=e[u].y;
        dfs1(y);
        tot[x]=tot[x]+tot[y];
        if (tot[son[x]]<tot[y]) son[x]=y;
    }
}
int tp;
void BT ()
{
    num=0;memset(last,-1,sizeof(last));
    s[++q].l=1;s[q].r=n;s[q].id=q;
    sort(s+1,s+1+q,cmp);
    S.push(1);
    for (int u=2;u<=q;u++)
    {
        int x;
        while (!S.empty())
        {
            x=S.top();
            if (s[x].r<s[u].l) S.pop();
            else break;
        }
        init(s[x].id,s[u].id);
        S.push(u);
    }
    tp=s[1].id;
    sort(s+1,s+1+q,cmpi);
    dfs1(tp);
}
int h[N];//这个东西出现了多少次
int cnt;
int cnt1[N];//清0标记 
LL lalal;//当前的答案是什么
int b[N];//这个数其实是什么
void add (int x)
{
    if (cnt1[x]!=cnt)//如果这个要请0
    {
        h[x]=0;
        cnt1[x]=cnt;
    }
    h[x]++;
    LL t=(LL)h[x]*b[x];
    if (t>lalal) lalal=t;
}
void change (int x)
{
    int y=son[x];
    if (y==0)
    {
        for (int u=s[x].l;u<=s[x].r;u++)
            add(a[u]);
    }
    else
    {
        for (int u=s[x].l;u<s[y].l;u++)
            add(a[u]);
        for (int u=s[y].r+1;u<=s[x].r;u++)
            add(a[u]);
    }
}
void dfs (int x)//哪一个点,他是不是重儿子 
{
    for (int u=last[x];u!=-1;u=e[u].last)
    {
        int y=e[u].y;
        if (y==son[x]) continue;
        dfs(y);
        cnt++;
    }
    if (son[x]!=0) dfs(son[x]);
    lalal=ans[son[x]];
    change(x);
    ans[x]=lalal;   
}
void LSH ()
{
    sort(b+1,b+1+n);
    int tot=1;
    for (int u=2;u<=n;u++)
        if (b[u]!=b[tot]) 
            b[++tot]=b[u];
    for (int u=1;u<=n;u++)
    {
        int l=1,r=tot;
        while (l<=r)
        {
            int mid=(l+r)>>1;
            if (b[mid]==a[u])   {a[u]=mid;break;}
            else if (b[mid]>a[u]) r=mid-1;
            else if (b[mid]<a[u]) l=mid+1;
        }
    }
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int main()
{
    n=read();q=read();
    int Q=q;
    for (int u=1;u<=n;u++)
    {
        a[u]=read();
        b[u]=a[u];
    }
    LSH();  
    for (int u=1;u<=q;u++)
    {
        s[u].l=read();s[u].r=read();
        s[u].id=u;
    }
    BT();
    dfs(tp);
    for (int u=1;u<=Q;u++) printf("%lld\n",ans[u]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值