[bzoj 1487] 无归岛

题目传送门
这道题目是一道不是很难的仙人掌dp题目,其实和bzoj 1040差不多,

首先得先证明该图是一个仙人掌图,
证明如下(参考该链接part1):
引理I:任意一个满足题意的小岛中生物个数必定是奇数
首先设这个图中有n个点,
其中任取一个点a,然后考虑其所有的邻居,因为邻居和a有且仅有一个共同邻居,所以会存在另一个a的邻居b与该邻居相连。这样这三个点就会两两相连,构成完美匹配。由此可推断出,a的邻居都可以这样两两在一起,和a构成三人组,
所以a的出度是偶数(其实无向图入度出度没区别),设a的邻居所构成的集合为N

考虑任取另一个点x,x不等于a,它肯定和a也有共同邻居,所以x与N必定有连边,这样就形成n-1条边(x可以取除了a之外的n-1个点),同时还有|N|条边,即邻居连向a的边。也就是整个图总共有|N|+n-1条边,不难发现|N|为偶数,因为a的出度为偶数。
同时|N|+n-1也为偶数,因为其为整个图中所有点入出度之和,所以n-1为偶数,
即n为奇数,证毕

所以一个岛上只能是奇数个动物,按上面的证明。小岛上只能够,一个动物与其他都是朋友,剩下的两两配对成为朋友。。即若干个三角形的环套在那个很多朋友的动物身上……(画画图就知道)
首先,仙人掌图不允许存在边有一个以上的简单回路经过,
而小岛内部的边只在那个三角形简单回路经过,所以满足条件。
连接小岛之间的边的话,可以仔细看题目,Neverland由一些岛屿环形排列组成,
所以这些边只会存在在大的回路中,满足条件,所以可以当成仙人掌图处理。
而仙人掌图的一贯做法是构建一棵DFS树,遍历时,把每个点在树上父亲记录下来,和DFS时间戳(访问次序)DFN记录下来。同时在树中会存在一些非树边,使树上的某些点被经过多次(树上的点构成环,那是因为图中存在环,使环上的点u有多条路径可以到达环上的点v)

现在看每一个小岛,易知在小岛中最先遍历到的点(即指在环里DFN最小的),无论是不是小岛所有三角环中的最高点,最终都能够遍历到小岛所有环,所以我们需要的即是在DFS过程中找到环的最高点和最低点,然后进行dp,把结果返回最高点。

最后,处理大环的时候,
题目中说到,整个小岛只会派一个代表和相邻小岛连接。
即最后一个小岛遍历时找到第一个小岛的点,就会把所有小岛上的多朋友的点,就是每个小岛中所有环的最高点,进行dp,返回根节点,这样就能得出答案。

这题是一道难得一见的好题,但很考察你的语文能力,和政治能力(充分利用材料),说句实话,题目提供的线索挺多的,让你可以很巧妙的解决问题。

最后,还有一句,inf不能取0x7fffffff,得取小一点,否则dp的时候可能会溢出。
还要注意如果取环上最高点时,其相邻点都不能够取

贴上代码:

#include 
#include 
#include 
#include 
#define MAXN 100005
#define MAXM 200005
const int inf =(1<<31)-1000;
using namespace std;

void read(int &x){
    char c=getchar();x=0;int flag=0;
    while(!isdigit(c)&&c!='-') c=getchar();
    if(c=='-'){flag=1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    if(flag)x=-x;
}

struct Edge{
    int v,nxt;
}g[2*MAXM];
int head[MAXN];
int cnt;

void addEdge(int u,int v){
    g[cnt]=(Edge){v,head[u]};
    head[u]=cnt++;
}

int n,m,x,y;
int a[MAXN];
int f[MAXN][2];
int DFN[MAXN],fa[MAXN],tot;
int u0,v0,u1,v1;

void dp(int u,int v){
    u0=v0=u1=v1=0;
    for(int i=v;i!=u;i=fa[i]){
        v1=u0+f[i][1];v0=u1+f[i][0];
        u0=v0;u1=max(v0,v1);
    }
    f[u][0]+=u1;
    u1=v0=v1=0;u0=-inf;
    for(int i=v;i!=u;i=fa[i]){
        v1=u0+f[i][1];v0=u1+f[i][0];
        u0=v0;u1=max(v0,v1);        
    }
    f[u][1]+=u0;
}

void dfs(int u){
    DFN[u]=++tot;
    for(int i=head[u];i!=-1;i=g[i].nxt){
        int v=g[i].v;
        if(!DFN[v]){
            fa[v]=u;
            dfs(v);
        }
    }
    f[u][1]=a[u];
    for(int i=head[u];i!=-1;i=g[i].nxt){
        int v=g[i].v;
        if(DFN[v]>DFN[u] && fa[v]!=u){
            dp(u,v);
        }
    }
}

int main(){
    memset(head,-1,sizeof(head));

    read(n);read(m);
    for(int i=1;i<=m;++i){
        read(x);read(y);
        addEdge(x,y);addEdge(y,x);
    }
    for(int i=1;i<=n;++i){
        read(a[i]);
    }
    dfs(1);
    printf("%d\n",max(f[1][0],f[1][1]));
}

你可能感兴趣的:(竞赛,--图,----仙人掌图)