JZOJ5442【NOIP2017提高A组冲刺11.1】荒诞 三进制状压+欧拉序

92 篇文章 1 订阅
73 篇文章 0 订阅

题意:我有一个n个点,m条边的无向图,第i个点建立一个旅游站点的费用是c_i。特别地,这张图中的任意两点间不存在节点数超过10的简单路径。
为了把一切都做得完善,为了使我感到不那么孤独,我想要建造一些旅游站点使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。我还希望这个建造方案总花费尽量少。
请求出这个花费。

czy大爷出的好题。
考场上有正解方向的想法,细节一脸懵逼。
首先15分的暴力DP(保证都是树)的思路可以应用到正解中。
10保证了每个连通块的最大深度不超过10,那么可以状压。
如同暴力DP一样,我们设0/1/2表示这个点未被覆盖/已被覆盖未选/已选。
然后考虑如何处理返祖边。
按照欧拉序遍历每个连通块,对于当前点x,枚举到状态s,考虑他的方案。

假如x不选择,那么如果x的祖先(包括他的父亲)有返祖边和他相连且状态为2,那么就可以转移到状态 s+3dep[x]1 含义是把x这个位置变为1.
否则就只能转移到状态s。

假如选择x,考虑返祖边,对于他的祖先(不包括他的父亲,或者说要单独拿出来处理),x状态肯定为2,他的祖先的状态也会因此而改变,原来为0的就会变成1.
这个地方单独记录一个ss表示选择以后他的祖先的状态改变,这里标解的实现非常巧妙,就是用一个桶来记录他的祖先状态,感觉非常perfect。
然后最后遍历他的儿子,注意到儿子会改变祖先的状态,所以我们在处理完他的子树以后要重新计算f,这里就很简单了。
f[x][s]=min(f[x][s+3dep[x]12],f[x][s+3dep[x]11])
表示当前点选或者不选。
答案就是 min(f[root][1],f[root][2]) .
最后注意一下边界,那些地方是dep[x],那些地方是dep[x]-1,考虑一下要不要枚举父亲即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
int n,m;
const int inf=0x3f3f3f3f;
const int M=6e4+5;
bool vis[N];
int head[N],next[N],go[N],ans;
int f[2][M],c[N],bin[20],fa[N],dep[N];
inline void pre(int x)
{
    vis[x]=1;
    dep[x]=dep[fa[x]]+1;
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (!vis[v])
        {
            fa[v]=x;
            pre(v);
        }
    }
}
int tot;
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline void dfs(int x)
{
    int now=dep[x]&1;
    bool flag[12];
    memset(flag,0,sizeof(flag));
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (dep[v]<dep[x])
            flag[dep[v]-1]=1;
    }
    fo(i,0,bin[dep[x]])f[now][i]=inf;
    fo(s,0,bin[dep[x]-1]-1)
    {
        if (f[now^1][s]>=inf)continue;
        int ss=s+1*bin[dep[x]-1]; 
        bool bz=0;
        fo(i,0,dep[x]-2)
        {
            if (flag[i])
            {
                if (!(ss/bin[i]%3))ss=ss+bin[i]*2;
                if ((s/bin[i]%3)==1)bz=1;
            }
        }
        f[now][ss]=min(f[now][ss],f[now^1][s]+c[x]);
        f[now][s+bin[dep[x]-1]*2*bz]=f[now^1][s];
    }
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (fa[v]==x)
        {
            dfs(v);
            fo(s,0,bin[dep[x]]-1)
            f[now][s]=min(f[now^1][s+1*bin[dep[x]]],f[now^1][s+2*bin[dep[x]]]);
        }
    }
}
int main()
{
    freopen("absurdity.in","r",stdin);
    freopen("absurdity.out","w",stdout);
    scanf("%d%d",&n,&m);
    bin[0]=1;
    fo(i,1,11)bin[i]=bin[i-1]*3;
    fo(i,1,n)scanf("%d",&c[i]);
    fo(i,1,m)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    int ans=0;
    fo(i,1,n)
    {
        if (!vis[i])
        {
            fa[i]=i;
            pre(i);
            memset(f,0x3f,sizeof(f));
            f[0][0]=0;
            dfs(i);
            ans+=min(f[1][1],f[1][2]);
        }
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值