NOI2014 魔法森林 LCT维护MST

BZOJ3669 题面


从更简单的情况入手,如果边权只有 a 没有b应该怎么处理?这时候问题就是找一条从1到N的路径,使得最长的边尽量短。根据最小生成树的性质,这样的边一定在最小生成树上。

如果 a 固定,得到的解法是一样的。那么可以分别讨论每一个a,对于权值不大于 a 的边对b做一次最小生成树。暴力做会超时。考虑到随着 a <script type="math/tex" id="MathJax-Element-573">a</script>的变大,可用的边也逐渐增多,那么只要在加边的同时更新最小生成树就可以了。这个操作可以用LCT处理。


#include<stdio.h>
#include<algorithm>
#define MAXN 150005
#define MAXM 200005
using namespace std;

int N,M,Ans=1e9;

struct node{int st,en,va,vb;}edge[MAXM];
bool operator<(node x,node y)
{
    if(x.va==y.va)return x.vb<y.vb;
    return x.va<y.va;
}

int ls[MAXN],rs[MAXN],fa[MAXN],rev[MAXN],val[MAXN];
int Max[MAXN],id[MAXN];

bool isrt(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}

void Update(int p)
{
    if(Max[ls[p]]>Max[rs[p]])Max[p]=Max[ls[p]],id[p]=id[ls[p]];
    else Max[p]=Max[rs[p]],id[p]=id[rs[p]];
    if(Max[p]<val[p])Max[p]=val[p],id[p]=p;
}

void Putdown(int p)
{
    if(rev[p]==0)return;
    rev[ls[p]]^=1;rev[rs[p]]^=1;rev[p]=0;
    swap(ls[p],rs[p]);
}

void Zig(int x)
{
    int y=fa[x],z=fa[y];
    if(!isrt(y))
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[rs[x]]=y;
    ls[y]=rs[x];rs[x]=y;
    Update(y);Update(x);
}

void Zag(int x)
{
    int y=fa[x],z=fa[y];
    if(!isrt(y))
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[ls[x]]=y;
    rs[y]=ls[x];ls[x]=y;
    Update(y);Update(x);
}

int s[MAXN],Top;
void Splay(int x)
{
    int i,y,z;

    s[++Top]=x;
    for(i=x;!isrt(i);i=fa[i])s[++Top]=fa[i];
    while(Top)Putdown(s[Top--]);

    while(!isrt(x))
    {
        y=fa[x];z=fa[y];
        if(isrt(y))
        {
            if(ls[y]==x)Zig(x);
            else Zag(x);
        }
        else
        {
            if(ls[z]==y)
            {
                if(ls[y]==x)Zig(y),Zig(x);
                else Zag(x),Zig(x);
            }
            else
            {
                if(rs[y]==x)Zag(y),Zag(x);
                else Zig(x),Zag(x);
            }
        }
    }
}

void Access(int x)
{
    int t=0;
    while(x)
    {
        Splay(x);
        rs[x]=t;Update(x);
        t=x;x=fa[x];
    }
}

void SetRt(int x)
{
    Access(x);Splay(x);rev[x]^=1;
}

void Link(int x,int y)
{
    SetRt(x);fa[x]=y;
}

void Cut(int x,int y)
{
    SetRt(x);
    Access(y);Splay(y);
    ls[y]=fa[x]=0;
    Update(y);
}

int FindRt(int x)
{
    Access(x);Splay(x);
    while(ls[x])x=ls[x];
    return x;
}

int tot,lk[MAXM][2];
void Add(int x,int y,int v)
{
    tot++;
    lk[tot][0]=x;lk[tot][1]=y;
    val[tot]=v;id[tot]=tot;
    Link(x,tot);Link(tot,y);
}

void Del(int x)
{
    Cut(x,lk[x][0]);Cut(x,lk[x][1]);
}

int GetMax(int x,int y,int op)
{
    SetRt(x);
    Access(y);Splay(y);
    if(op==1)return Max[y];
    else return id[y];
}

int main()
{
    int i,j;

    scanf("%d%d",&N,&M);
    for(i=1;i<=M;i++)scanf("%d%d%d%d",&edge[i].st,&edge[i].en,&edge[i].va,&edge[i].vb);

    sort(edge+1,edge+M+1);

    tot=N;
    for(i=j=1;i<=50000;i++)
    {
        while(j<=M&&edge[j].va<=i)
        {
            int x,y;

            if(edge[j].st==edge[j].en){j++;continue;}

            x=FindRt(edge[j].st);
            y=FindRt(edge[j].en);
            if(x!=y)Add(edge[j].st,edge[j].en,edge[j].vb);
            else
            {
                int tmp=GetMax(edge[j].st,edge[j].en,1);
                if(tmp>edge[j].vb)
                {
                    tmp=GetMax(edge[j].st,edge[j].en,2);
                    Del(tmp);Add(edge[j].st,edge[j].en,edge[j].vb);
                }
            }
            j++;
        }
        if(FindRt(1)!=FindRt(N))continue;
        Ans=min(Ans,i+GetMax(1,N,1));
    }

    if(Ans==1e9)puts("-1");
    else printf("%d\n",Ans);
}
P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值