图论练习

图论练习

学长的题果然恐怖如斯

CF1458D Flip and Reverse

这个操作看着很奇怪

\(0\)先看成\(-1\)

那可操作的区间就是和为\(0\)

对每一个前缀和的值建点,把每种元素看作边

那可操作的区间就是一个环

然后你会发现一个操作就是相当于是反着走这个环

这里我们考虑连成无向边

考虑连边的过程,除了起点和终点,每个点的度数都是偶数

那对于\(x\rightarrow(x-1)\),和\(x\rightarrow(x+1)\),如果\(x\)不是起点终点

那一定会有\((x-1)\rightarrow x\),\((x+1)\rightarrow x\)

如果走\(x-1\)是我原本的路径

\(x\rightarrow(x-1)\rightarrow x\rightarrow(x+1)\rightarrow x\)

反向就是\(x\rightarrow(x+1)\rightarrow x\rightarrow(x-1)\rightarrow x\)

由此对于原图任意一条欧拉路都是满足的

对此我们只需贪心跑欧拉路即可,具体就是看跑\(0\)是不是还回得到\(0\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int T;
int n;
string S;
int cnt_node;
map<int,int>V;
int Edge[MAXN][2];
int Cdg[MAXN][2];
int Tp=0;
void dfs(int x)
{
    if(Tp==n)
    {
        return;
    }
    if(Cdg[x][0]&&Cdg[Edge[x][0]][1])
    {
        Cdg[x][0]--;
        ++Tp;
        printf("0");
        dfs(Edge[x][0]);
    }
    else if(Cdg[x][1])
    {
        Cdg[x][1]--;
        ++Tp;
        printf("1");
        dfs(Edge[x][1]);
    }
    else if(Cdg[x][0])
    {
        Cdg[x][0]--;
        ++Tp;
        printf("0");
        dfs(Edge[x][0]);
    }
}
int main()
{
    freopen("date.in","r",stdin);
    freopen("date.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        V.clear();
        for(int i=1;i<=cnt_node;i++)
        {
            Edge[i][0]=0;
            Edge[i][1]=0;
            Cdg[i][0]=0;
            Cdg[i][1]=0;
        }
        cnt_node=0;
        cin>>S;
        V[0]=++cnt_node;
        int Now=0;
        n=S.size();
        for(int i=0;i<S.size();i++)
        {
            int Op=S[i]-'0';
            int Vp=(Op==1)?1:-1;
            if(!V[Now+Vp])
            {
                V[Now+Vp]=++cnt_node;
            }
            Edge[V[Now]][Op]=V[Now+Vp];
            Cdg[V[Now]][Op]++;
            Now+=Vp;
        }
        Tp=0;
        dfs(1);
        printf("\n");


    }

}

CF827F Dirty Arkady's Kitchen

一眼看上去很像魔改最短路

但由于无法停留,所以这里如果是把横跳放在点上是行不通的,事实上这里和最短路也没太大关系,因为需要维护的是到达该点的时间区间

由于要反复横跳,所以这里我们需要奇偶分类

可以发现每个点的时间戳是几段区间

这里考虑动态加边,也就是我们考虑对\(l\)排序

可以发现如果对于边排序的话,这里有个优美的性质

考虑\(e_i\)\(u,v\),你会发现\(u\)的最早到达时间一定小于等于\(l\)

考虑归纳证明,可以发现每个节点的最早都是\(l+1\)的形式,这里奇偶分类,所以是一定满足的

这启示我们记录动态加边时的最晚到达时间,如果其大于等于\(l\)则当前边可行并更新,这里我们可以反复横跳来达到目的

否则这里我们可以把这条不满足的边先存起来附在\(u\)上,等我们更新了\(u\)的最晚到达时间,我们就可以再把它加进去,不过他的\(l\)必须更新,因为我们要利用这个\(l\)算最早到达时间

可以发现每条边最多算两次

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,m;
struct Edge{
    int u,v,l,r,op;
    bool operator<(const Edge x)const{
        return l>x.l;
    }
};
int x,y,l,r;
int f[MAXN][2];
vector<Edge>Att[MAXN][2];
int main()
{
    freopen("date.in","r",stdin);
    freopen("date.out","w",stdout);
    memset(f,-1,sizeof(f));
    priority_queue<Edge>q;
    while(q.size())
    {
        q.pop();
    }
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d %d",&x,&y,&l,&r);
        q.push((Edge){x,y,(l)&1?(l):(l+1),(r-1)&1?(r-1):(r-2),1});
        q.push((Edge){x,y,(l)&1?(l+1):(l),(r-1)&1?(r-2):(r-1),0});
        q.push((Edge){y,x,(l)&1?(l):(l+1),(r-1)&1?(r-1):(r-2),1});
        q.push((Edge){y,x,(l)&1?(l+1):(l),(r-1)&1?(r-2):(r-1),0});
    }
    f[1][0]=0;
    if(n==1)
    {
        printf("0");
        return 0;
    }
    while(q.size())
    {
        Edge temp=q.top();
        q.pop();
        if(temp.l>temp.r)
        {
            continue;
        }
        if(f[temp.u][temp.op]>=temp.l)
        {
            int Tx=temp.r+1;
            if(temp.v==n)
            {
                printf("%d",temp.l+1);
                return 0;
            }
            if(Tx>=f[temp.v][temp.op^1])
            {               
                f[temp.v][temp.op^1]=Tx;
                for(int i=0;i<Att[temp.v][temp.op^1].size();i++)
                {
                    Att[temp.v][temp.op^1][i].l=temp.l+1;
                    q.push(Att[temp.v][temp.op^1][i]);
                }
                Att[temp.v][temp.op^1].clear();
            }
        }
        else
        {
            Att[temp.u][temp.op].push_back(temp);
        }
    }
    printf("-1");
}

CF1307F Cow and Vacation

每个加油站的可达性是可以划分等价类的

但对于每个点可达的范围是不能划分的

这里我们考虑拆分路径为\(\dfrac{k}{2}\)(方便\(bfs\),其实可以随便拆),为了解决小数问题可以先拆点

然后这里可以维护每个加油站距离为\(\dfrac{k}{2}\)的点并将其划分为等价类

我们发现两个点的可达性关键在于这是否存在两个点走\(\dfrac{k}{2}\)后在相同等价类

然后这里我们还可以发现对于大部分\((x,y)\),实际上我们就是要尽量往上爬

可以发现走的这\(\dfrac{k}{2}\)一定是朝着对方走的,具体证明画画图就行了

然后用倍增维护即可,注意会往下走

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5;
int n,k,r;
int m;
int x,y;
int fa[MAXN];
int cnt_node;
vector<int>g[MAXN];
int find(int x)
{
    if(fa[x]==x)
    {
        return fa[x];
    }
    fa[x]=find(fa[x]);
    return fa[x];
}
void unionn(int i,int j)
{
    fa[find(i)]=find(j);
    return;
}
int Dis[MAXN];
int dep[MAXN];
int dp[MAXN][21];
void bfs(int root)
{
    queue<int>q;
    q.push(root);
    dep[root]=1;
    while(q.size())
    {
        int temp=q.front();
        q.pop();
        for(int i=0;i<g[temp].size();i++)
        {
            int v=g[temp][i];
            if(dep[v])
            {
                continue;
            }
            dep[v]=dep[temp]+1;
            q.push(v);
            dp[v][0]=temp;
            for(int j=1;j<=20;j++)
            {
                dp[v][j]=dp[dp[v][j-1]][j-1];
            }
        }
    }
}
int LCA(int a,int b)
{
    if(dep[a]<dep[b])
    {
        swap(a,b);
    }
    for(int i=20;i>=0;i--)
    {
        if(dep[dp[a][i]]>=dep[b])
        {
            a=dp[a][i];
        }
    }
    if(a==b)
    {
        return a;
    }
    for(int i=20;i>=0;i--)
    {
        if(dp[a][i]!=dp[b][i])
        {
            a=dp[a][i];
            b=dp[b][i];
        }
    }
    return dp[a][0];
}
int Leap(int x,int d)
{
    for(int i=20;i>=0;i--)
    {
        if((d>>i)&1)
        {
            x=dp[x][i];
        }
    }
    return x;
}
int main()
{
    freopen("date.in","r",stdin);
    freopen("date.out","w",stdout);
    scanf("%d %d %d",&n,&k,&r);
    cnt_node=n;
    for(int i=1;i<n;i++)
    {
        scanf("%d %d",&x,&y);
        ++cnt_node;
        g[x].push_back(cnt_node);
        g[cnt_node].push_back(x);
        g[y].push_back(cnt_node);
        g[cnt_node].push_back(y);
        
        
    }
    for(int i=1;i<=cnt_node;i++)
    {
        fa[i]=i;
    }
    queue<pair<int,int> >q;
    for(int i=1;i<=r;i++)
    {
        scanf("%d",&x);
        q.push(make_pair(x,0));
    }
    
    while(q.size())
    {
        pair<int,int> temp=q.front();
        q.pop();
        //cerr<<"fuck";
        //cerr<<temp.first<<' '<<temp.second<<endl;
        if(temp.second==k)
        {
            continue;
        }
        
        for(int i=0;i<g[temp.first].size();i++)
        {
            int v=g[temp.first][i];
            unionn(v,temp.first);
            if(Dis[v])
            {
                continue;
            }
            Dis[v]=temp.second+1;
            q.push(make_pair(v,Dis[v]));
        }
    }
    
    scanf("%d",&m);
    bfs(1);
    while(m--)
    {
        scanf("%d %d",&x,&y);
        int Lca=LCA(x,y);
        int dist=dep[x]+dep[y]-dep[Lca]*2;
        if(dist<=2*k)
        {
            printf("YES\n");
        }
        else
        {
            int K=k;
            int Dx=(dep[x]-dep[Lca]);
          //  printf("%d???\n",Dx);
            int Tx;
            if(Dx>=k)
            {
                Tx=Leap(x,k);
            }
            else{
            //    printf("???\n");
                k-=Dx;
                int Txp=(dep[y]-dep[Lca]-k);
                Tx=Leap(y,Txp);
            }
            k=K;
            int Dy=(dep[y]-dep[Lca]);
            int Ty;
            if(Dy>=k)
            {
                Ty=Leap(y,k);
            }
            else
            {
                k-=Dy;
                int Typ=(dep[x]-dep[Lca]-k);
                Ty=Leap(x,Typ);
            }
            k=K;
        //    printf("%d %d %d %d %d %d??\n",Tx,Ty,k,Lca,find(Tx),find(Ty));
            if(find(Tx)==find(Ty))
            {
                printf("YES\n");
            }
            else{
                printf("NO\n");
            }
        }
        

    }   
}

CF1556G Gates to Another World

第一步都没想到。。。

把原题抽象到线段树上

这里如果要往上跳就必须满足兄弟子树还有值?

好像有点问题

考虑原问题相当于是对于每个线段树的节点左右子树对应叶子连边

这里我们可以处理出每个区间可能保留的时间,数量级大概是\(m\)

然后我们可以直接保留这个区间,区间就代表所有叶子,然后按上述方法连边即可

最后倒着动态加边即可

这里的边数是\(mn\)的,因为每个叶子节点连的边的数量是它祖先的数量

#include<bits/stdc++.h>
#define ls Tree[p].lc
#define rs Tree[p].rc
using namespace std;
const int MAXN=2e5+5;
int n;
int m;
long long L[MAXN];
long long R[MAXN];
int Idt[MAXN];
long long lsh[MAXN];
long long x,y;
int T;
char s[105];
vector<pair<long long,long long> >Query[MAXN];
int Tdt[MAXN];
struct Seg{
    int lc,rc;
    int T;
}Tree[MAXN*50];
int root;
int cnt_node;
void Update(int &p,long long l,long long r,long long ql,long long qr,int T)
{
    if(!p)
    {
        p=++cnt_node;
       // printf("%d %lld %lld-----\n",p,l,r);
    }
    if(l>=ql&&r<=qr)
    {
        Tree[p].T=T;
        return;
    }//15192848
    long long mid=(l+r)>>1ll;
    if(ql<=mid)
    {
        Update(ls,l,mid,ql,qr,T);
    }
    if(qr>mid)
    {
        Update(rs,mid+1,r,ql,qr,T);
    }
}
int Find(int p,long long x,long long l,long long r)
{
    if(l==r)
    {
        return p;
    }
    long long mid=(l+r)>>1ll;
   // printf("%lld gkdktdktpdp\n",mid);
    if(x<=mid)
    {
        if(ls)
        {
            return Find(ls,x,l,mid);
        }
        else{
            return p;
        }
    }
    else{
       // printf("%d>>>>rkskks\n",rs);
        if(rs)
        {
            return Find(rs,x,mid+1,r);
        }
        else{
            return p;
        }
    }
}
int Leaf(int x)
{
    if((!Tree[x].lc)&&(!Tree[x].rc))
    {
        return 1;
    }
    return 0; 
}
struct Edge{
    int u,v;
};
vector<Edge>G[MAXN];

void dfs(int l,int r)
{
    if(Leaf(l)&&Leaf(r))
    {
        int Tfp=min(Tree[l].T,Tree[r].T);
        G[Tfp].push_back((Edge){l,r});
        return;
    }
    else if((!Leaf(l))&&Leaf(r))
    {
        dfs(Tree[l].lc,r);
        dfs(Tree[l].rc,r);
    }
    else if((Leaf(l))&&(!Leaf(r)))
    {
        dfs(l,Tree[r].lc);
        dfs(l,Tree[r].rc);
    }
    else
    {
        dfs(Tree[l].lc,Tree[r].lc);
        dfs(Tree[l].rc,Tree[r].rc);
    }
    
}
int fa[MAXN*50];
int find(int x)
{
    if(fa[x]==x)
    {
        return fa[x];
    }
    fa[x]=find(fa[x]);
    return fa[x];
}
void unionn(int i,int j)
{
    fa[find(i)]=find(j);
    return;
}
int main()
{
  //  freopen("date.in","r",stdin);
  //  freopen("date.out","w",stdout);
    scanf("%d %d",&n,&m);
    int cnt_lsh=0;
    lsh[++cnt_lsh]=0;
    lsh[++cnt_lsh]=(1ll<<n);
    int cnt_w=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        if(s[0]=='b')
        {
            scanf("%lld %lld",&x,&y);
            lsh[++cnt_lsh]=x;
            lsh[++cnt_lsh]=y+1;
            L[++cnt_w]=x;
            R[cnt_w]=y;
            Idt[cnt_w]=i;
            
        }
        else
        {
            scanf("%lld %lld",&x,&y);
            Query[i].push_back(make_pair(x,y));
        }
    }
    sort(lsh+1,lsh+1+cnt_lsh);
    cnt_lsh=unique(lsh+1,lsh+1+cnt_lsh)-lsh-1;
    for(int i=1;i<cnt_lsh;i++)
    {
        Tdt[i]=m+1;
    }
    for(int i=1;i<=cnt_w;i++)
    {
         int Tcf=lower_bound(lsh+1,lsh+1+cnt_lsh,L[i])-lsh;
         Tdt[Tcf]=Idt[i]-1;
    }
    for(int i=1;i<cnt_lsh;i++)
    {
        Update(root,0,(1ll<<n)-1,lsh[i],lsh[i+1]-1,Tdt[i]);
    }
    for(int i=1;i<=cnt_node;i++)
    {
        dfs(Tree[i].lc,Tree[i].rc);
        fa[i]=i;
    }
    vector<int>Ra;
    Ra.clear();
    for(int i=m+1;i>=1;i--)
    {
        for(int j=0;j<G[i].size();j++)
        {
            Edge tp=G[i][j];
         //   printf("%d %drueurueur\n",tp.u,tp.v);
            //printf("???\n");
            unionn(tp.u,tp.v);
        }
        for(int j=0;j<Query[i].size();j++)
        {
            pair<long long,long long>Toc=Query[i][j];
            if(find(Find(root,Toc.first,0,(1ll<<n)-1))==find(Find(root,Toc.second,0,(1ll<<n)-1)))
            {
                Ra.push_back(1);
            }
            else
            {
                Ra.push_back(0);
            }
        }
    }
    for(int i=Ra.size()-1;i>=0;i--)
    {
        printf("%d\n",Ra[i]);
    }
}
posted @ 2023-07-04 08:28  kid_magic  阅读(14)  评论(0编辑  收藏  举报