CSP模拟58联测20 T3 注视一切的终结

CSP模拟58联测20 T3 注视一切的终结

题面及数据范围

Ps:链接为衡水中学OJ。

去除重边以后是树,而我们需要使一个点到另外一个点的简单路径上相邻边的颜色尽可能不相同。

发现如果一条边有 3 3 3 种或以上的颜色,那么该边肯定可以与相邻边不同,所以把 ≥ 3 \geq3 3 的情况均看为 3 3 3 的情况。

然后把边权下压至儿子的点权。

可以 l o g 2 n log_2n log2n 的倍增求出 l c a lca lca 同时也可以通过倍增求最大贡献。

f [ u ] [ i ] [ a ] [ b ] f[u][i][a][b] f[u][i][a][b] 表示从 u u u 向上跳 2 i 2^i 2i 步,开始颜色为 a a a,结束颜色为 b b b 的最大贡献,其中 a ≤ 3 , b ≤ 3 a \leq 3,b \leq 3 a3,b3。(实际上已经跳到了 2 i + 1 2^i+1 2i+1 这个点,但因为点权是连向父亲的边权,所以我们不需要 2 i + 1 2^i+1 2i+1 这个点的点权)

初始时 f [ u ] [ 0 ] [ a ] [ b ] = c o l [ a ] ≠ c o l [ b ] f[u][0][a][b]=col[a]\neq col[b] f[u][0][a][b]=col[a]=col[b]

转移时枚举 x , y = f a [ x ] [ i − 1 ] , z = f a [ x ] [ i ] x,y=fa[x][i-1],z=fa[x][i] x,y=fa[x][i1],z=fa[x][i] ,设颜色为 a , b , c a,b,c a,b,c f [ x ] [ i ] [ a ] [ c ] = max ⁡ ( f [ x ] [ i − 1 ] [ a ] [ b ] + f [ y ] [ i − 1 ] [ b ] [ c ] ) f[x][i][a][c]=\max(f[x][i-1][a][b]+f[y][i-1][b][c]) f[x][i][a][c]=max(f[x][i1][a][b]+f[y][i1][b][c])

其中 z z z 是用来枚举终点的颜色。

对于查询,先求出 l c a lca lca

如果 l c a = x lca=x lca=x l c a = y lca=y lca=y 那么直接求出另一点的 l c a lca lca 的最大值;否则在枚举最后两条边的颜色。

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

const int maxn=5e5+5;

struct node
{
    int to,nxt,w;
}edge[maxn*4];

int n,m,tot;
int head[maxn],dp[maxn][22][4][4],f[maxn][21],deep[maxn],col[maxn][5],cx[5],cy[5],cnt[maxn];

bool vis[maxn];

void add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].nxt=head[x];
    edge[tot].w=z;
    head[x]=tot;
}

void dfs1(int u,int fa)
{
    vis[u]=true;
    for(int i=head[u];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==fa)
        {
            if(cnt[u]==3) continue;
            bool flg=1;
            for(int j=0;j<cnt[u];j++) if(col[u][j]==edge[i].w){flg=0;break;}
            if(flg) col[u][cnt[u]++]=edge[i].w;
        }
        else if(!vis[v]) dfs1(v,u);
    }
}
void dfs2(int u,int fa)
{
    vis[u]=true;
    deep[u]=deep[fa]+1;
    f[u][0]=fa;
    if(u!=1)
    {
        for(int a=0;a<cnt[u];a++)//初始化
            for(int b=0;b<cnt[fa];b++)
                dp[u][0][a][b]=(col[u][a]!=col[fa][b]);
    }
    for(int i=1;(1<<i)<=deep[u];i++)//dp 转移
    {
        f[u][i]=f[f[u][i-1]][i-1];
        int y=f[u][i-1],z=f[u][i];
        for(int a=0;a<cnt[u];a++)//a,b,c 均枚举颜色
            for(int b=0;b<cnt[y];b++)
                for(int c=0;c<cnt[z];c++) dp[u][i][a][c]=max(dp[u][i][a][c],dp[u][i-1][a][b]+dp[y][i-1][b][c]);
    }
    for(int i=head[u];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(vis[v]) continue;
        dfs2(v,u);
    }
}

int Lca(int x,int y)
{
    if(deep[x]<deep[y]) swap(x,y);
    for(int i=20;i>=0;i--) if(deep[f[x][i]]>=deep[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int jump(int x,int dis,int c[])//点 x 向上跳 dis 步的最大答案
{
    int now=x;
    for(int i=0;i<=20;i++)
    {
        if(((dis>>i)&1)==0) continue;
        int cc[5];
        memset(cc,0,sizeof(cc));
        for(int a=0;a<cnt[now];a++)
            for(int b=0;b<cnt[f[now][i]];b++) cc[b]=max(cc[b],c[a]+dp[now][i][a][b]);
        memcpy(c,cc,sizeof(cc));
        now=f[now][i];
    }
    return now;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }

    dfs1(1,0);
    memset(vis,0,sizeof(vis));
    dfs2(1,0);

    int q;
    scanf("%d",&q);
    for(int qq=1;qq<=q;qq++)
    {
        int x,y;
        memset(cx,0,sizeof(cx)),memset(cy,0,sizeof(cy));
        scanf("%d%d",&x,&y);
        int lca=Lca(x,y),fx,fy,ans=0;
        if(x!=lca) fx=jump(x,deep[x]-deep[lca]-1,cx);
        if(y!=lca) fy=jump(y,deep[y]-deep[lca]-1,cy);
        if(x==lca) for(int i=0;i<cnt[fy];i++) ans=max(ans,cy[i]);
        else if(y==lca) for(int i=0;i<cnt[fx];i++) ans=max(ans,cx[i]);
        else
            for(int a=0;a<cnt[fx];a++)
                for(int b=0;b<cnt[fy];b++) ans=max(ans,cx[a]+cy[b]+(col[fx][a]!=col[fy][b]));//最后枚举颜色
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值