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

发布时间 2023-10-19 19:38:18作者: 彬彬冰激凌

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

题面及数据范围

Ps:链接为衡水中学OJ。

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

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

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

可以 \(log_2n\) 的倍增求出 \(lca\) 同时也可以通过倍增求最大贡献。

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

初始时 \(f[u][0][a][b]=col[a]\neq col[b]\)

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

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

对于查询,先求出 \(lca\)

如果 \(lca=x\)\(lca=y\) 那么直接求出另一点的 \(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);
    }
}