JZOJ 5442. 【NOIP2017提高A组冲刺11.1】荒诞

题目

有一个n个点,m条边的无向图,第i个点建立一个旅游站点的费用是c_i。特别地,这张图中的任意两点间不存在节点数超过10的简单路径。
我想要建造一些旅游站点使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。我还希望这个建造方案总花费尽量少。
请求出这个花费。
数据范围
对于前30%的测试点,满足1<=n<=20,0<=m<=50。
对于另外15%的测试点,满足每个连通块都是一棵树。
对于100%的测试点,满足
1<=n<=2104,0<=m<=2.5104,0<=ci<=104

题解

30分肯定枚举选哪个不选哪个。
另15分直接树形DP。

AC做法

题目条件

选了一个点,与之相连的点可以不选。
以任何一点为根的dfs树深度不超过10。

困惑点

有返祖边干扰了转移。
只确定当前点的状态满足不了题目的需求。

准确做法

考虑树形DP。
考虑怎么样避免返祖边的干扰。一个新知识:欧拉序。
欧拉序,就是从根遍历到每个点,再回到根的经过的点的序列。
如果第二次到点x,说明x的子树已经处理完了,可以更新到x。
那么可以考虑基于欧拉序的DP。
f[x][s] 表示做到x,x到根节点的点状态为s(s为三进制数,0表示没有覆盖到也没选,1表示被覆盖到,2表示被选)
那么枚举状态s,讨论x是否被选两种情况就可以DP。(设x的父亲是y)
①没有被选:由于返祖边的干扰,我们先确定与之有边连着的祖先是否被选,如果是的话x一定会被覆盖到 f[x][s+3dep[x]1]f[y][s] ,否则不会 f[x][s]f[y][s]
②被选:那么会影响到与之有边连着的祖先。
设这个新状态为s1,则有 f[x][s1]f[y][s]+c[x]
接下来递归子树。由于第一次遍历到x点时为讨论到x的子树更新x的情况,所以 f[x][s] 要重新被算一次。由于x的祖先转移到x的情况已经传给了x的子树,所以递归回来的时候(设x的儿子为y)直接 f[x][s]=min(f[y][s+3dep[y]1],f[y][s+23dep[y]1])

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 20010
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
struct note{
    int to,next;
};note edge[N*5/2];
int i,j,k,l,n,m;
int dep[N],ans,fa[N];
int c[N],u,v;
int head[N],tot;
int f[2][59050];
int _3[13];
bool bz[N],bb[13];
int read(){
    int fh=1,res=0;char ch;
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return res*fh;
}
void lb(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void mix(int &x,int y){x=x<y?x:y;}
void dg(int x){
    bz[x]=1;
    int o=dep[x]&1;
    bool pp;
    memset(f[o],63,sizeof(f[o]));
    memset(bb,0,sizeof(bb));
    int i,s,s1;
    for(i=head[x];i;i=edge[i].next)
        if(dep[edge[i].to]<dep[x])bb[dep[edge[i].to]]=1;
    fo(s,0,_3[dep[x]]-1)
    if(f[1^o][s]<1061109567){
        s1=s+_3[dep[x]]*2;
        pp=0;
        fo(i,1,dep[x]-1)
            if(bb[i]){
                if(((s1/_3[i])%3)==0)s1+=_3[i];
                if(((s/_3[i])%3)==2)pp=1;
            }
        mix(f[o][s1],f[1^o][s]+c[x]);
        if(pp)mix(f[o][s+_3[dep[x]]],f[1^o][s]);
         else mix(f[o][s],f[1^o][s]);
    }
    for(i=head[x];i;i=edge[i].next)
        if(!bz[edge[i].to]){
            fa[edge[i].to]=x;
            dep[edge[i].to]=dep[x]+1;
            dg(edge[i].to);
            fo(s,0,_3[dep[edge[i].to]]-1)
                f[o][s]=min(f[1^o][s+_3[dep[edge[i].to]]],f[1^o][s+_3[dep[edge[i].to]]*2]);
        }
}
int main(){
    _3[1]=1;fo(i,2,12)_3[i]=_3[i-1]*3;
    n=read(),m=read();
    fo(i,1,n)c[i]=read();
    fo(i,1,m){
        u=read(),v=read();
        lb(u,v),lb(v,u);
    }
    fo(i,1,n)
        if(!bz[i]){
            memset(f,63,sizeof(f));
            f[0][0]=0;
            dep[i]=1;
            dg(i);
            ans+=min(f[1][1],f[1][2]); 
        }
    printf("%d",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值