day5

孤人 提交于 2019-11-26 14:07:12

t1

给出一张有n个点m条边的无向图,现在给出起点s和终点t,每个点还存在一个点权,用01表示,询问在起点带的最少的钱,到达终点的前不少于w,每到达一个点,如果当前点权为1,就减少钱1,否则减少钱\(\lceil \frac{w}{k} \rceil\),并且还要求输出字典序最小的路径,\(n\leq 5\times 10^5,m\leq 10^6\)

法一:

显然刚开始的前,不妨设为\(e\),不好确定,而又具有单调性,因为e增大剩下的前肯定增大,否则减小,于是可以二分确定e,确定了e以后,现在问题就变成找出一条最优秀的路径,这显然是最短路问题,考虑优先队列bfs,因为钱是单调递减的,于是当前的状态能够到达的最优的状态,就是起点能够到达的最优状态,而对于字典序,只要考虑优先队列当中最小的相同的钱所在的点,取字典序最小那个,最终就可以保证路径字典序的最小(所以不能使用\(A^*\)),最终时间复杂度为\(nlog(n)^2\)

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
#define Size 500500
#define llmax 4611686018427387904
using namespace std;
template<class free>
struct heap{
    free a[Size];int n;
    il void push(free x){
        a[++n]=x;ri int p(n);
        while(p>1)
            if(a[p]<a[p>>1])
                swap(a[p],a[p>>1]),
                    p>>=1;
            else break;
    }
    il void pop(){
        a[1]=a[n--];ri int p(1),s(2);
        while(s<=n){
            if(s<n&&a[s+1]<a[s])++s;
            if(a[s]<a[p])
                swap(a[s],a[p]),
                    p=s,s<<=1;
            else break;
        }
    }
};
struct pi{
    ll x;int y,z;
    il bool operator<(const pi&a){
        return x==a.x?y<a.y:x>a.x;
    }
};
struct point{
    point*next;int to;
}*head[Size];
int s,t,w,k;
heap<pi>H;
int pre[Size];
bool a[Size],check[Size];
void print(int);
il bool bfs(ll);
template<class free>
il void read(free&);
il void link(int,int);
int main(){
    int n,m;
    read(n),read(m),read(k);
    for(int i(1);i<=n;++i)read(a[i]);
    for(int i(1),u,v;i<=m;++i)
        read(u),read(v),link(u,v),link(v,u);
    read(s),read(t),read(w);ll l(0),mid,r(llmax);
    while(l<=r){mid=l+r>>1;
        if(bfs(mid))l=mid+1;
        else r=mid-1;
    }printf("%lld\n",l);
    print(pre[t]),printf("%d",t);
    return 0;
}
void print(int x){
    if(!x)return;
    print(pre[x]);
    printf("%d->",x);
}
il bool bfs(ll money){
    memset(check,0,sizeof(check));
    H.n=0,H.push({money,s,0});pi s;
    while(H.n){
        s=H.a[1],H.pop();if(check[s.y])continue;
        check[s.y]=true,pre[s.y]=s.z;if(s.y==t)return s.x<w;
        for(point *i(head[s.y]);i!=NULL;i=i->next)
            if(!check[i->to])H.push(
                {s.x-(a[i->to]?1:(s.x+k-1)/k),i->to,s.y});
    }
}
il void link(int u,int v){
    head[u]=new point{head[u],v};
}
template<class free>
il void read(free &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

法二:

最短路问题,一定注意从起点出发和从终点出发是等价的,于是我们考虑从终点出发,显然终点只需要带w钱,然后不断倒推,求出到起点的最优解即可,代价是单调的,显然可以利用优先队列bfs解决问题。

现在关键是如何确定到达下一个点还剩多少钱,显然关键在于当前所在的点的权值恰好为0如何确定下一个点的钱,不妨设下一个点钱为x,当前钱为y,显然是解整除方程

\(y=x-\lceil \frac{x}{k} \rceil=x-\lfloor \frac{x+k-1}{k} \rfloor=x-(\frac{x+k-1}{k}-\{\frac{x+k-1}{k}\})=\)

\(x-(\frac{x+k-1}{k}-\frac{(x+k-1)\%(k-1)}{k})\Rightarrow x=\frac{ky}{k-1}+1-\frac{(x+k-1)\% (k-1)}{k-1}\)

此时容易知道\((x+k-1)\% (k-1)<k-1\),所以\(\frac{(x+k-1)\% (k-1)}{k-1}\in [0,1)\),所以式子的后半部分全部\(\in(0,1]\),而x必须为一个整数,所以\(\frac{ky}{k-1}+[0,1)=\lceil \frac{ky}{k-1} \rceil\)

于是我们可以倒着优先队列bfs,这样可以在\(O(nlog(n))\)的时间内求出最优解,但是注意到无法输出最优的方案,但是我们晓得了要带的钱,可以顺着最短路,求出最小字典序。

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
#define Size 500500
using namespace std;
template<class free>
struct heap{
    free a[Size];int n;
    il void push(free x){
        a[++n]=x;ri int p(n);
        while(p>1)
            if(a[p]<a[p>>1])
                swap(a[p],a[p>>1]),
                    p>>=1;
            else break;
    }
    il void pop(){
        a[1]=a[n--];ri int p(1),s(2);
        while(s<=n){
            if(s<n&&a[s+1]<a[s])++s;
            if(a[s]<a[p])
                swap(a[s],a[p]),
                    p=s,s<<=1;
            else break;
        }
    }
};
bool flag;
struct pi{
    ll x;int y,z;
    il bool operator<(const pi&a){
        return (x==a.x?y<a.y:x<a.x)^flag;
    }
};
struct point{
    point*next;int to;
}*head[Size];
il ll bfs1();
void print(int);
il void bfs2(ll);
int s,t,w,pre[Size],k;
bool a[Size],check[Size];
template<class free>
il void read(free&);
il void link(int,int);
int main(){
    int n,m;read(n),read(m),read(k);
    for(int i(1);i<=n;++i)read(a[i]);
    for(int i(1),u,v;i<=m;++i)
        read(u),read(v),link(u,v),link(v,u);
    read(s),read(t),read(w);ll ans(bfs1());
    printf("%lld\n",ans),bfs2(ans);
    return 0;
}
void print(int x){
    if(pre[x])print(pre[x]),printf("%d->",pre[x]);
}
heap<pi>H;
il void bfs2(ll x){
    flag=true,memset(check,0,sizeof(check));
    H.n=0,H.push({x,s,0});pi s;
    while(H.n){
        s=H.a[1],H.pop();if(check[s.y])continue;
        check[s.y]=true,pre[s.y]=s.z;
        if(s.y==t)return print(t),(void)printf("%d",t);
        for(point*i(head[s.y]);i!=NULL;i=i->next){
            if(check[i->to])continue;
            H.push({s.x-(a[i->to]?1:
                         (s.x+k-1)/k),i->to,s.y});
        }
    }
}
il ll bfs1(){
    H.push({w,t});pi s;
    while(H.n){
        s=H.a[1],H.pop();if(check[s.y])continue;
        check[s.y]=true;if(s.y==::s)return s.x;
        for(point *i(head[s.y]);i!=NULL;i=i->next){
            if(check[i->to])continue;
            H.push({a[s.y]?s.x+1:(s.x*k+k-2)/(k-1),i->to});
        }
    }
}
il void link(int u,int v){
    head[u]=new point{head[u],v};
}
template<class free>
il void read(free &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

t2

给出一个有n个点m条边的无向图,有边权,已经给出,m只有两种取值即\(n-1\ and\ n\),定义一个点的权值为,从该点出发,随机选择一条边移动,走简单路径而且走到死的路径长度的数学期望,求点权的平均值,\(n\leq 10^6\)

显然这是一个基环树问题,先考虑树的分数。

显然走到死,就是走到叶子节点,于是只要求出每个点为根节点到达叶子节点的数学期望即可,不妨设\(f_i\)表示从以i为根的子树中从i走到叶子节点的路径长度数学期望,显然有(定义\(w[i][j]\)为i,j间边长,deg[i]为i的点度,root为钦定的一个根节点)

\[f_i=\frac{\sum_{j\in son(i)}f_j+w[i][j]}{deg[i]-(i!=root)}\]

然后考虑换根法,从根x换到根y,设\(g[x]\)表示x作为整个树的根的到达所有叶子节点的数学期望,显然有

\[g[y]=\frac{\frac{(g[x]\times deg[x]-(f[y]+w[x][y]))}{deg[x]-1}+w[x][y]+f[y]\times (deg[y]-1)}{deg[y]}\]

然后我们就可以在\(O(n)\)的时间内解出树的答案,而且还不要特判叶子节点。

对于环的分数,显然是一个环上面吊着很多树,考虑如何从一个点i出发求出数学期望,记为\(h_i\),假设从i出发,现在到达点j,到j的概率为\(gl\),我们有

\(h_i+=\sum\frac{gl}{deg[j]}\times w[i][k]\)(其中k为与j相连的点)

其实原理就是期望\(E=\sum p_ix_i\),其中\(x_i\)可以被拆分成多条边,而拆分出来对于一条边就是到达这条边的概率乘以这条边的长度,于是期望可以单独对于一个部分考虑,只要该部分的长度乘以概率即可。

但是这样显然是\(O(n^2)\),考虑优化,分为环和树处理。

对于树我们先维护\(f_i\),表示以i为根节点的子树中,从i的儿子出发,到达叶子节点的数学期望之和,显然这个前面已经讲述了维护方式,对于环而言,我们维护一个\(g_i\)表示从环上第i个点出发,其中树中的一条边不走,走简单路径走到死的路径长度的数学期望,特别地其中数学期望在树上的部分还没有进行计算。

先考虑求\(g_i\)的顺时针方向的部分,逆时针方向一样,两者加起来就是答案,不妨已经暴力求出了\(g_i\),现在考虑求\(g_{i+1}\),设i进入i-1子树的概率为\(gl\),进行以下操作就可以求出\(g_{i+1}\)

\[g_{i+1}=(g_i-gl\times f[i-1])\times (deg[i]-1)-w[i][i+1]-\frac{f[i+1]}{deg[i+1]-1}+\]
\[\frac{gl\times (deg[i-1]-2)(deg[i]-1)}{deg[i-1]-1}\times (f[i-1]+w[i-1][i])+\frac{gl\times (deg[i-1]-2)(deg[i]-1)}{(deg[i-1]-1)(deg[i]-2)}\times f[i]\]

然后我们就求出了f,g,考虑如何计算最终的答案,枚举环上的每个点,然后下树,记录该点的父亲到根节点的概率\(gl\),特别地,当点i为根节点时\(gl=1\),然后顺便换根求出以该点为根的儿子到整棵树的数学期望之和,记作\(hl\),答案累加\(\frac{hl}{deg[i]}+\frac{gl}{deg[i]}\times g[i]\),特别地当这个点就为根节点的时候应该累加\(\frac{hl+g[i]\times (deg[i]-1)}{deg[i]}\),然后我们就可以\(O(n)\)解决这道题目(常数奇大无比)。

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define il inline
#define ri register
#define ll long long
#define Size 1005000
const int yyb(1000000007);
using namespace std;
struct point{
    point*next;int to,w;
}*head[Size];
int lsy,n,m,deg[Size],iv[Size],
    f[Size],g[Size],vis[Size],
    hu[Size],hw[Size],w[Size],
    fa[Size],ht,ans;
il int mod(int);
il void read(int&),init(),
    link(int,int,int),work1(),work2(),
    get_g();
void dfs(int,int),dfs1(int,int),dfs2(int),
    dfs3(int),dfs4(int,int,int);
int main(){init();
    if(m==n-1)work1();
    else if(m==n)work2();
    return 0;
}
void dfs4(int x,int top,int gl){
    if(x==top)f[x]=0;vis[x]=1;
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(vis[i->to])continue;
        f[x]=(f[x]+(ll)f[i->to]*iv[deg[i->to]-1]+i->w)%yyb;
    }if(x==top)ans=(ans+(f[x]+(ll)g[x]*(deg[x]-1)%yyb)*iv[deg[x]])%yyb;
    else ans=(ans+(ll)gl*iv[deg[x]]%yyb*g[top]+(ll)iv[deg[x]]*f[x])%yyb;
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(vis[i->to])continue;
        f[i->to]=((ll)(f[x]-((ll)f[i->to]*iv[deg[i->to]-1]+
                            i->w))%yyb*iv[deg[x]-1]+i->w)%yyb;
        dfs4(i->to,top,(ll)gl*((x^top)?iv[deg[x]-1]:1)%yyb);
    }
}
void dfs3(int x){vis[x]=1;
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(vis[i->to])continue;dfs3(i->to);
        f[x]=(f[x]+(ll)f[i->to]*
              iv[deg[i->to]-1]%yyb+i->w)%yyb;
    }
}
il void get_g(){
    int gl(1),cnt(0);
    for(int i(1);i<=ht;++i)
        cnt=(cnt+(ll)gl*iv[deg[hu[i]]-1-(i==ht)]%yyb
             *(f[hu[i]]*(i!=1)+hw[i]*(i!=ht)))%yyb,
            gl=(ll)gl*iv[deg[hu[i]]-1]%yyb;
    g[hu[1]]=(g[hu[1]]+cnt)%yyb;
    for(int i(2),la;i<=ht;++i){
        la=i-2;if(!la)la+=ht;
        gl=(ll)gl*(deg[hu[i-1]]-1)%yyb,cnt=(ll)cnt*(deg[hu[i-1]]-1)%yyb;
        cnt=(cnt-hw[i-1]-(ll)iv[deg[hu[i]]-1]*f[hu[i]])%yyb;
        cnt=(cnt-(ll)gl*(deg[hu[la]]-1)%yyb*iv[deg[hu[la]]-2]%yyb*f[hu[la]])%yyb;
        cnt=(cnt+(ll)gl*(f[hu[la]]+hw[la]))%yyb;
        cnt=(cnt+(ll)gl*iv[deg[hu[i-1]]-2]%yyb*f[hu[i-1]])%yyb;
        g[hu[i]]=(g[hu[i]]+cnt)%yyb,gl=(ll)gl*iv[deg[hu[i-1]]-1]%yyb;
    }
}
void dfs2(int x){
    vis[x]=1;
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(fa[x]==i->to)continue;
        if(vis[i->to]){int tp(x);
            while(tp!=i->to)
                hu[++ht]=tp,hw[ht]=w[tp],
                    tp=fa[tp];
            hu[++ht]=i->to,hw[ht]=i->w;
            fa[i->to]=x;continue;
        }fa[i->to]=x,w[i->to]=i->w,dfs2(i->to);
    }
}
il void work2(){
    dfs2(1),memset(vis,0,sizeof(vis));
    for(int i(1);i<=ht;++i)vis[hu[i]]=1;
    for(int i(1);i<=ht;++i)dfs3(hu[i]);
    get_g(),reverse(hu+1,hu+ht+1);
    reverse(hw+1,hw+ht),get_g();
    memset(vis,0,sizeof(vis));
    for(int i(1);i<=ht;++i)vis[hu[i]]=1;
    for(int i(1);i<=ht;++i)dfs4(hu[i],hu[i],1);
    printf("%d",mod((ll)ans*iv[n]%yyb));
}
il int mod(int x){
    return (x%yyb+yyb)%yyb;
}
void dfs1(int x,int pa){
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(i->to==pa)continue;
        g[i->to]=(((ll)g[x]*deg[x]-(ll)(f[i->to]+i->w))%yyb
                  *iv[deg[x]-1]+i->w+(ll)f[i->to]*(deg[i->to]-1)
                  %yyb)%yyb*iv[deg[i->to]]%yyb,
            dfs1(i->to,x);
    }
}
void dfs(int x,int pa){
    for(point*i(head[x]);i!=NULL;i=i->next){
        if(i->to==pa)continue;dfs(i->to,x);
        f[x]=(f[x]+f[i->to]+i->w)%yyb;
    }f[x]=(ll)f[x]*iv[deg[x]-(pa>0)]%yyb;
}
il void work1(){
    dfs(1,0),g[1]=f[1],dfs1(1,0);int ans(0);
    for(int i(1);i<=n;++i)ans=(ans+g[i])%yyb;
    printf("%d",mod((ll)ans*iv[n]%yyb));
}
il void link(int u,int v,int w){
    head[u]=new point{head[u],v,w};
}
il void init(){
    read(lsy),read(n),read(m);
    for(int i(1),u,v,w;i<=m;++i)
        read(u),read(v),read(w),
            link(u,v,w),link(v,u,w),
            ++deg[u],++deg[v];
    iv[1]=1;for(int i(2);i<=n;++i)
                iv[i]=-(ll)iv[yyb%i]*(yyb/i)%yyb;
}
il void read(int &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

t3

给出一个长度为n排列\(\{a_i\}\),给出一个长度为n01序列\(\{b_i\}\),\(b_i\)为1表示应该保留\(a_i\),反之,确定一个区间长度x,每次操作可以选择任意一个区间,删去其中最小的数字,然后右边的数字补齐,询问最大的x保证最后排列变为要保留的数字,而删去所有不需要保留的数字,\(n\leq 10^7\)

显然不能暴力模拟,考虑从小到大选择要删去的\(a_i\),显然最优解中一定存在小的先被删去,然后再删去次小。

我们不妨维护一个\(l_i,r_i\),表示i这个要删去的数字,向左延伸的第一个位置不被删去而比\(a_i\)小,\(r_i\)表示向右延伸到第一个位置不被删去,但是比\(a_i\)小,然后顺便记录\((l_i,r_i)\)这个开区间当中所有的比\(a_i\)大的数字,记为\(bl\),然后对于每一个要删去的位置的\(bl\)取min就是最终答案,这样我们就得到了\(O(n^2)\)暴力,考虑如何优化。

参考代码:

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define Size 10050
#define intmax 0x7fffffff
using namespace std;
int a[Size];bool b[Size];
template<class free>
il void read(free&);
int main(){
    int n,ans(intmax);read(n);
    for(int i(1);i<=n;++i)read(a[i]);
    for(int i(1);i<=n;++i)read(b[i]);
    b[0]=b[n+1]=1;
    for(int i(1),l,r,t;i<=n;++i){
        if(b[i])continue;l=r=i,t=1;
        while(!(b[l]&&a[l]<a[i]))t+=b[l]||a[l]>a[i],--l;
        while(!(b[r]&&a[r]<a[i]))t+=b[r]||a[r]>a[i],++r;
        ans=min(ans,t);
    }printf("%d",ans);
    return 0;
}
template<class free>
il void read(free &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

显然我们需要快速求出\(l_i,r_i\)以及中间的数字个数,这是一个单调性的问题,我们考虑用单调数据结构维护,不妨从小往大考虑数字,如果当前数字需要保留,就将位置加入平衡树,否则,在平衡树查找比该数字位置恰好小和大的两个位置,这就是我们需要求的\(l_i,r_i\),特别的如果没有,就代表是边界,不妨刚开始就在平衡树中加入位置\(0,n+1\),而正确性在于平衡树现在拥有所有比该数字小的数字。

现在考虑如何查询\((l_i,r_i)\)中满足条件的数字,而直接求不好求,考虑逆向思维,求出不需要的数字,也就是要删去比它小的数字,而注意到我们已经是从小到大考虑数字,所以前面的数字必然都要比该个数字小,于是我们只需要维护位置即可,每次了来一个要被删去的数字,在其对应的位置上+1,然后查\((l,r)\)就可以得到,这样就得到一个常数很大的\(nlog(n)\)

参考代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#define il inline
#define ri register
#define Size 1000500
#define intmax 0x7fffffff
using namespace std;
struct data{
    int a,c;bool b;
}d[Size];
set<int>S;
int a[Size],n,ans(intmax);
set<int>::iterator l,m,r;
il int ask(int);
template<class free>
il void read(free&);
il void change(int,int);
il bool comp(const data&,const data&);
int main(){
    read(n),++n;
    for(int i(2);i<=n;++i)read(d[i].a),d[i].c=i;
    for(int i(2);i<=n;++i)read(d[i].b);++n;
    d[1]={0,1,1},d[n]={0,n,1},sort(d+1,d+n+1,comp);
    for(int i(1);i<=n;++i)
        if(d[i].b)S.insert(d[i].c);
        else{
            m=S.insert(d[i].c).first,l=r=m,--l,++r;
            ans=min(ans,*r-*l-1-(ask(*r)-ask(*l-1))),
                change(*m,1),S.erase(m);
        }printf("%d",ans);
    return 0;
}
il int ask(int p){
    int ans(0);while(p)ans+=a[p],p-=-p&p;return ans;
}
il void change(int p,int x){
    while(p<=n)a[p]+=x,p+=-p&p;
}
il bool comp(const data&a,const data&b){
    return a.a<b.a;
}
template<class free>
il void read(free &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

考虑进一步优化,发现一个性质,就是对于一个要被删去的数字i的\((l_i,r_i)\)而言,如果这个开区间范围内存在一个要被删除的数字j比i大,那么\((l_j,r_j)\)必然被\((l_i,r_i)\)包含,于是j必然成为最优解,不断递归下去最终得到一个k,\((l_k,r_k)\)中不包含一个要被删除的数字比它大,于是我们只要考虑求出每个要被删除的数字的\((l_i,r_i)\)中不要被删除的比它大的数字,这个显然维护出前缀和\(s_i\)表示位置i前面不要被删除的数字,查询\(s_{r_i-1}-s_{l_i}\),关键在求\(l_i,r_i\),不妨一次求一个,先考虑求\(l_i\),从左往右扫描,考虑数字i,现在关键是找到最近的一个位置保证该数字不被删除,而且要小于i,考虑离散化维护树状数组,维护区间最大值,每次来一个不要被删除的位置,直接在该个数值对应的位置,将其位置加入,于是查询,只要查询,\(1\sim i\)的最大值就可以得到答案,同理\(r_i\)也是一个方法,于是我们得到一个常数很小的\(O(nlog(n))\)

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define Size 1005000
#define intmax 0x7fffffff
using namespace std;
bool b[Size],flag;
int a[Size],c[Size],n,l[Size],s[Size];
template<class free>
il void read(free&);il int ask(int);
il void change(int,int),cmp(int&,int);
int main(){
    read(n);int ans(intmax);
    for(int i(1);i<=n;++i)read(a[i]);
    for(int i(1);i<=n;++i)read(b[i]);
    for(int i(1);i<=n;++i){
        s[i]=s[i-1]+b[i];
        if(b[i])change(a[i],i);
        else l[i]=ask(a[i]);
    }for(int i(1);i<=n;++i)c[i]=n+1;
    flag=1;for(int i(n);i;--i){if(b[i])change(a[i],i);
        else ans=min(ans,s[ask(a[i])-1]-s[l[i]]+1);
    }printf("%d",ans);
    return 0;
}
il int ask(int p){
    int ans((flag?n+1:0));
    while(p)cmp(ans,c[p]),p-=-p&p;
    return ans;
}
il void cmp(int &p,int x){
    if(flag)p=p<x?p:x;
    else p=p>x?p:x;
}
il void change(int p,int x){
    while(p<=n)cmp(c[p],x),p+=-p&p;
}
template<class free>
il void read(free &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

考虑进一步优化,显然题目需要\(O(n)\),考虑是否算重,对于已经被i访问过的端点,如果j来访问,发现还可以延伸,那么必然j小于端点对应的数字,而\((l_i,r_i)\)间的可以被计算的数字必然比端点大,于是全部可以成为j的答案,于是i必然比j优秀。

显然一个数字所访问的位置需要被标记,如果直接从左往右扫描,然后延伸,会存在有数字直接在被标记的位置上,这样优化效果就不明显,更换扫描顺序,从大的数字枚举到小的数字,这样如果有数字出现在被标记的位置,且结果还是会更加优秀,因此只要从大往小扫描,暴力扩展并且标记,如果遇到标记,就考虑下一个数字,就可以做到\(O(n)\)

参考代码:

#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define Size 10000050
#define intmax 0x7fffffff
using namespace std;
int a[Size],b[Size],c[Size],d[Size];
il void read(int&);
int main(){
    int n,ans(intmax);read(n);
    for(ri int i(1);i<=n;++i)read(a[i]);
    for(ri int i(1);i<=n;++i)
        read(b[i]),c[a[i]]=i;b[0]=b[n+1]=1;
    for(ri int i(n),l,r,s;i;--i){
        if(b[c[i]])continue;l=r=c[i],--l,++r,s^=s;
        while(!(b[l]&&a[l]<a[c[i]])){if(d[l])goto end;s+=b[l],d[l]=i,--l;}
        while(!(b[r]&&a[r]<a[c[i]])){if(d[r])goto end;s+=b[r],d[r]=i,++r;}
        ans=min(ans,s+1);end:;
    }printf("%d",ans);
    return 0;
}
il void read(int &x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!