【LGR-062】洛谷10月月赛 III div.2 C D

小C与桌游

题意

有向无环图,走到一个能够更新当前走过的点标号最大值的点,就可以加一或者减一。求每次都加能加最多,每次都减能减最少。

有趣的题目,满脑子都是哈尔滨重现了!!
这种思维题比哈尔滨 I I I简单一点,但是我依然没想出来,虽然已经特别接近了。
要求找到一个策略:

首先肯定要是拓扑序的走,那么队列有多个点,应该选哪一个呢?
当前最大值为 M M M,如果比 M M M小的点,可以随便放,显然不会影响结果还可以增加选择。

如果是前者,加最多的话,接下来要选的尽可能小。
如果是后者,减最少的话,接下来的要选的尽可能大,但是要先做最前面的操作。
这正是我所忽略的,因为前面的操作可能会产生更大的点。

#include<bits/stdc++.h>
using namespace std;
vector<int> v;

const int maxn = 5e5+400;
typedef long long ll;

struct node1{
    int x;
    friend bool operator < (node1 a,node1 b){
        return a.x>b.x;
    }
};

struct node2{
    int x;
    friend bool operator < (node2 a,node2 b){
        return a.x<b.x;
    }
};

priority_queue<node1,vector<node1> >p;
set<int>q;

int n,m;
int ind[maxn],inq[maxn];
int ans1,ans2;
vector<int>G[maxn];

void topo1(){
    for(int i=1;i<=n;i++){
        if(ind[i])continue;
        p.push(node1{i});
    }
    int now=0;
    while(!p.empty()){
        int u=p.top().x;p.pop();
        if(u>now)ans1++;
        now=max(now,u);
        for(auto v:G[u]){
            ind[v]--;
            if(!ind[v])p.push(node1{v});
        }
    }
    cout<<ans1<<endl;
}

void topo2(){
    for(int i=1;i<=n;i++){
        if(inq[i])continue;
        q.insert(i);
    }
    int now=0;
    while(!q.empty()){
        int u;
        if(*q.begin()<now){
            u=*q.begin();
            q.erase(u);
        }
        else{
            u=*(--q.end());
            q.erase(u);
        }
        if(u>now)ans2++;
        now=max(now,u);
        for(auto v:G[u]){
            inq[v]--;
            if(!inq[v])q.insert(v);
        }
    }
    cout<<ans2<<endl;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;scanf("%d%d",&u,&v);
        G[u].push_back(v);
        ind[v]++,inq[v]++;
    }
    topo1();
    topo2();
}

小O与排列

题意

有个排列 p p p,有一个序列 a a a,要求支持对 a a a的单点修改,询问区间内存不存在,对于区间内一个点, p [ a [ i ] ] p[a[i]] p[a[i]]也出现在这个区间内

题意

有一说一 一般这都是套路题,首先对于每个点作为 p p p的时候,只有最近的有效,左边最近和右边最近,这都是可以预处理出来的。

那怎么处理修改呢?
修改的操作我们考虑到,实际上对于每个 a a a,其他的 p p p显然是最近的有效,其他的可以不更新,这样更新就可以在多项式复杂度内解决了。

细节较多。

首先作为 p p p,这个点的左部分和右部分要删除。然后再更新。
作为 a a a,原来的最近 p p p部分左右都要删除,并且对于左和右分别要更新。同时删除之后,可以 a a a的前面一个 a a a和后面一个 a a a可能会得到新的最近的 p p p(比之前会远),所以找到这两个点更新。
更新之后,也可能产生新的最近点。也要找到左边右边的这个点类似于二操作并更新。

具体怎么更新一个点呢,更新的时候要保证最近,所以首先找到离自己最近的点,比如找左边的,当前位置为5,值 3 3 3,最近的 3 3 3的位置为2,那么找到的最近 p [ 3 ] p[3] p[3]只有超过 2 2 2的时候才是最近的,才需要更新。

总之是比较复杂的,不过对于 a a a也只有这两种情况,也不是很复杂,只是最后一种情况一开始忽略了。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

const int maxn = 500050;
typedef long long ll;

inline int read(){
    int num=0,w=0;
    char ch=0;
    while(!isdigit(ch)){
        w|=ch=='-';
        ch=getchar();
    }
    while(isdigit(ch)){
        num=(num<<3)+(num<<1)+(ch^48);
        ch=getchar();
    }
    return w?-num:num;
}

int n,m,T=1;
int p[maxn],a[maxn],t[maxn];
set<int>s[maxn];

struct tree2{
    tree2 *lson,*rson;
    int val1,val2;
}dizhi[maxn<<1],*root=&dizhi[0];

void push_up(tree2 *tree){
    tree->val1=max(tree->lson->val1,tree->rson->val1);//Left Point
    tree->val2=min(tree->lson->val2,tree->rson->val2);//Right Point
}

void build(tree2 *tree,int l,int r){
    tree->val1=0;
    tree->val2=n+1;
    if(l==r)return ;
    int mid=(l+r)>>1;
    tree->lson=&dizhi[T++];
    tree->rson=&dizhi[T++];
    build(tree->lson,l,mid);
    build(tree->rson,mid+1,r);
}

void update(tree2 *tree,int l,int r,int pos,int val,int op){
    if(l==r){
        if(op==1)tree->val1=val;
        if(op==2)tree->val2=val;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)update(tree->lson,l,mid,pos,val,op);
    else update(tree->rson,mid+1,r,pos,val,op);
    push_up(tree);
}

int get_val(tree2 *tree,int l,int r,int x,int y,int op){
    if(x<=l&&r<=y){
        if(op==1)return tree->val1;
        if(op==2)return tree->val2;
    }
    int mid=(l+r)>>1;
    int t1,t2;
    if(op==1)t1=t2=0;
    if(op==2)t1=t2=n+1;
    if(x<=mid)t1=get_val(tree->lson,l,mid,x,y,op);
    if(y>mid)t2=get_val(tree->rson,mid+1,r,x,y,op);
    if(op==1)return max(t1,t2);
    if(op==2)return min(t1,t2);
}

void deal_set(int x,int y){
    s[a[x]].erase(x);
    a[x]=y;
    s[a[x]].insert(x);
}

void slove(int pos,int op){
    set<int>::iterator it,iter;
    if(op==1){
        int LT=-1,v;
        it=s[a[pos]].lower_bound(pos);
        if(it!=s[a[pos]].begin())LT=*(--it);
        iter=s[p[a[pos]]].upper_bound(pos);
        if(iter!=s[p[a[pos]]].begin()){
            v=*(--iter);
            if(v>LT)update(root,1,n,pos,v,1);
        }
    }
    else{
        int RT=n+1,v;
        it=s[a[pos]].upper_bound(pos);
        if(it!=s[a[pos]].end())RT=*it;
        iter=s[p[a[pos]]].lower_bound(pos);
        if(iter!=s[p[a[pos]]].end()){
            v=*iter;
            if(v<RT)update(root,1,n,pos,v,2);
        }
    }
}

int main(){
    cin>>n>>m;
    build(root,1,n);
    for(int i=1;i<=n;i++)p[i]=read(),t[p[i]]=i;
    for(int i=1;i<=n;i++)a[i]=read(),s[a[i]].insert(i);
    for(int i=1;i<=n;i++)slove(i,1),slove(i,2);
    for(int i=1;i<=m;i++){
        int op=read(),x=read(),y=read();
        if(op==1){
            set<int>::iterator re;
            int L=0,R=0,wl=0,wr=0;
            re=s[a[x]].lower_bound(x);
            if(re!=s[a[x]].begin()){
                wl=*(--re);
            }
            re=++s[a[x]].lower_bound(x);
            if(re!=(s[a[x]].end())){
                wr=*(re);
            }
            set<int>::iterator tl,tr;
            int tmp=t[a[x]];
            tl=s[tmp].upper_bound(x);
            if(tl!=s[tmp].begin()){
                L=*(--tl);
                update(root,1,n,L,n+1,2);
            }
            tr=s[tmp].lower_bound(x);
            if(tr!=s[tmp].end()){
                R=*tr;
                update(root,1,n,R,0,1);
            }//删除原来a[x]的影响,只考虑最近的点对。
            deal_set(x,y);
            if(wl)slove(wl,2);
            if(wr)slove(wr,1);
            if(L)slove(L,2);
            if(R)slove(R,1);
            update(root,1,n,x,0,1);
            update(root,1,n,x,n+1,2);
            slove(x,1),slove(x,2);
            L=0,R=0;
            tmp=t[a[x]];
            tl=s[tmp].upper_bound(x);
            if(tl!=s[tmp].begin()){
                L=*(--tl);
                slove(L,2);
            }
            tr=s[tmp].lower_bound(x);
            if(tr!=s[tmp].end()){
                R=*tr;
                slove(R,1);
            }
            //for(int i=1;i<=n;i++)cout<<get_val(root,1,n,i,i,1)<<" "<<get_val(root,1,n,i,i,2)<<endl;
        }
        else{
            if(get_val(root,1,n,x,y,1)>=x||get_val(root,1,n,x,y,2)<=y)puts("Yes");
            else puts("No");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值