CF891C Envy

一、题目

点此看题

二、解法

很容易看出来是 l c t lct lct吧,我们先随便搞一个最小生成树。

询问时尝试加入给的边,如果给定的边已经在最小生成树中是可以的。然后判断连成的环中最大边权是否等于这条边,那我们就删去环上最大的边,然后加入这条边,把这条边打上不可替换标记,也就是把权值赋值为 − 1 -1 1,最后再改回去。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),但是会 T T T,贴个代码吧,正解在下面 q w q qwq qwq

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
int read()
{
 int x=0,flag=1;char c;
 while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
 while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
 return x*flag;
}
int n,m,par[M],ch[M][2],val[M],Max[M],fl[M],st[M];
bool use[M];int q,a[M],b[M],c[M],d[M],id[M];
int nrt(int x)
{
    return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)
{
    return ch[par[x]][1]==x;
}
void push_up(int x)
{
    if(!x) return ;
    Max[x]=max(val[x],max(Max[ch[x][0]],Max[ch[x][1]]));
    if(Max[x]==val[x]) id[x]=x;
    if(Max[x]==Max[ch[x][0]]) id[x]=id[ch[x][0]];
    if(Max[x]==Max[ch[x][1]]) id[x]=id[ch[x][1]];
}
void flip(int x)
{
    if(!x) return ;
    swap(ch[x][0],ch[x][1]);
    fl[x]^=1;
}
void push_down(int x)
{
    if(!x) return ;
    if(fl[x])
    {
        flip(ch[x][0]);flip(ch[x][1]);
        fl[x]=0;
    }
}
void rotate(int x)
{
    int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
    ch[y][k]=w;par[w]=y;
    if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
    ch[x][k^1]=y;par[y]=x;
    push_up(y);push_up(x);
}
void splay(int x)
{
    int y=x,z=0;
    st[++z]=y;
    while(nrt(y)) st[++z]=y=par[y];
    while(z) push_down(st[z--]);
    while(nrt(x))
    {
        int y=par[x],z=par[y];
        if(nrt(y))
        {
            if(chk(x)==chk(y)) rotate(y);
            else rotate(x);
        }
        rotate(x);
    }
}
void access(int x)
{
    for(int y=0;x;x=par[y=x])
        splay(x),ch[x][1]=y,push_up(x);
}
void makeroot(int x)
{
    access(x);splay(x);
    flip(x);
}
int findroot(int x)
{
    access(x);splay(x);
    while(ch[x][0]) push_down(x),x=ch[x][0];
    splay(x);
    return x;
}
void split(int x,int y)
{
    makeroot(x);
    access(y);splay(y);
}
void link(int x,int y)
{
    makeroot(x);
    if(findroot(y)!=x) par[x]=y;
}
void cut(int x,int y)
{
    makeroot(x);
    if(findroot(y)==x && par[y]==x && !ch[y][0])
    {
        par[y]=ch[x][1]=0;
        push_up(x);
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        a[i]=read()+m;b[i]=read()+m;c[i]=val[i]=read();
        push_up(i);
        if(findroot(a[i])^findroot(b[i]))
            link(a[i],i),link(b[i],i),use[i]=1;
        else
        {
            split(a[i],b[i]);
            if(Max[b[i]]<=val[i]) continue;
            int t=id[b[i]];//树的形态会改变,存下来
            cut(a[t],t);
            cut(b[t],t);
            link(a[i],i);link(i,b[i]);
            use[t]=0;use[i]=1;
        }
    }
    q=read();
    while(q--)
    {
        int k=read(),flag=0;
        for(int j=1;j<=k;j++)
        {
            int i=read();d[j]=i;
            if(use[i])
            {
                makeroot(i);
                val[i]=-1;
                push_up(i);
                continue;
            }
            split(a[i],b[i]);
            if(Max[b[i]]==val[i])
            {
                int t=id[b[i]];
                cut(a[t],t);
                cut(b[t],t);
                link(a[i],i);link(i,b[i]);
                use[t]=0;use[i]=1;
                makeroot(i);
                val[i]=-1;
                push_up(i);
                continue;
            }
            flag=j;
            break;
        }
        if(!flag) puts("YES"),flag=k+1;
        else puts("NO");
        for(int j=1;j<flag;j++)
        {
            makeroot(d[j]);
            val[d[j]]=c[d[j]];
            push_up(d[j]);
        }
        for(int j=flag+1;j<=k;j++)
            read();
    }
}

正解需要对 K r u s k a l Kruskal Kruskal 算法有更深的一些理解,首先由这样一些结论:

  • 对于任意权值,最小生成树中这种权值的边数是一定的
  • 对于任意正确加边方案,加到某一种权值图的连通性是一定的

综合上面两种结论,我们可以推知判断某些边是否能同时出现在最小生成树中,不同权值的边是不会相互影响的,所以这告诉我们可以分权值讨论。

我们先建一颗最小生成树,记录下每条边连接的两个并查集的根,询问的时候先把权值排序,相同权值的放在一起做,这里我们需要连接预处理存下来的两个并查集的根(因为我们需要在以前的基础上做),判断能否连接,如果构成了环就说明不满足条件。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),但是要比 l c t lct lct快多了,好写多了。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int M = 500005;
int read()
{
 int x=0,flag=1;char c;
 while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
 while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
 return x*flag;
}
int n,m,q,p[M];
struct edge
{
    int u,v,c,id,tx,ty;
    bool operator < (const edge &B) const
    {
        return c<B.c;
    }
}e[M];
void make()
{
    for(int i=1;i<=n;i++) p[i]=i;
}
int find(int x)
{
    if(p[x]^x) p[x]=find(p[x]);
    return p[x];
}
void merge(int x,int y)
{
    p[find(x)]=find(y);
}
bool cmp(edge x,edge y)
{
    return x.id<y.id;
}
int main()
{
    n=read();m=read();
    make();
    for(int i=1;i<=m;i++)
    {
        e[i].u=read();e[i].v=read();e[i].c=read();
        e[i].id=i;
    }
    sort(e+1,e+1+m);
    for(int i=1;i<=m;)
    {
        int j=i;
        for(;e[i].c==e[j].c;j++)
        {
            e[j].tx=find(e[j].u);
            e[j].ty=find(e[j].v);
        }
        for(;i<j;i++)
        {
            if(find(e[i].u)^find(e[i].v))
                merge(e[i].u,e[i].v);
        }
    }
    q=read();
    sort(e+1,e+1+m,cmp);
    make();
    while(q--)
    {
        int k=read();
        vector<edge> g;
        for(int i=1;i<=k;i++)
        {
            int x=read();
            g.push_back(edge{e[x].tx,e[x].ty,e[x].c,0,0,0});
        }
        sort(g.begin(),g.end());
        bool flag=1;
        for(int i=0;i<k && flag;)
        {
            int j=i;
            while(j<k && g[j].c==g[i].c)
            {
                if(find(g[j].u)==find(g[j].v))
                {
                    flag=0;
                    break;
                }
                merge(g[j].u,g[j].v);
                j++;
            }
            for(;i<j;i++)
                p[g[i].u]=g[i].u,p[g[i].v]=g[i].v;
        }
        if(flag) puts("YES");
        else puts("NO");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值