链接:http://codeforces.com/contest/1220/problem/E
题意:n个点m条边的无向图,每个点有一个权值,从给定的某点出发去遍历这个图,一条边不能连续走2次,比如有一条从a点到b点的边,你不能从a到b再到a,问你遍历到的点的最大权值和是多少。
题解:想了一天后1A了,太激动了!!!!!
1.首先需要双连通分量缩点,这个毫无疑问,因为在一个双连通分量内,你可以从任意点到任意点。
双连通分量:在这个分量内,任意两个点之间至少有两条路径。
2.第1步完成后,你就可以做出一个树,每个节点都是一个双连通分量,树的根节点是起点所在的双连通分量。
3.然后你思考一下,给你一棵树,你从根节点出发,有哪些双连通分量是从根节点到达它后又能回到根节点呢?答案是所有的包含两个及以上节点的双连通分量与根节点之间的所有节点(这里说的节点指的是缩点前的节点)。假设你现在所在的双连通分量为a,前往的双连通分量为b,假如b包含1个节点,那么你从a到b之后不能立刻回到a,这样会连续走两遍连接a和b的边。但是如果b包含2个及以上节点,就避免了连续走两遍连接a和b的边。
4.对第3步做一个总结,就是所有的可以从根节点遍历到又能回到根节点的双连通分量是连通的。(再解释一下:假设根节点所在的双连通分量为a,现在有双连通分量b,如果可以从a到b再回到a,那么b就是可以从根节点遍历到又能回到根节点的双连通分量)
5.把第4步所说的连通的分量记作S。
6.那么其实就是以S为根节点,然后和剩余的双连通分量构成一个树,原问题转化为从S出发,一条边不能连续走两遍,所能遍历到的点的最大权值和。这就很简单了,dfs一下,找到一个从根节点到叶子节点的路径上所有点的最大权值和就好了。
写完这个感觉自己表述能力太差了,如果想和我交流一下想法联系QQ:1274843323。
AC代码
#include
using namespace std ;
typedef long long ll ;
const int maxn = 5e5 + 5 ;
const int maxm = 5e5 + 5 ;
int num[maxn] ;
ll wei[maxn] , wei1[maxn] ;
bool vis[maxn] ;
bool vis1[maxn] ;
bool judge[maxn] ;
ll two = 0 ;
ll ans[maxn] ;
ll max1 = 0 ;
int s ;
struct Edge
{
int to , next ;
bool cut ;
} edge[maxm] , edge1[maxm] ;
int head[maxn] , tot , tot1 , head1[maxn] ;
int low[maxn] , dfn[maxn] , stack1[maxn] , belong[maxn] ;
int index , top ;
int block = 0 ;
int bridge ;
bool instack[maxn] ;
void addedge(int u , int v)
{
edge[tot].to = v ;
edge[tot].next = head[u] ;
edge[tot].cut = false ;
head[u] = tot ++ ;
}
void addedge1(int u , int v)
{
edge1[tot1].to = v ;
edge1[tot1].next = head1[u] ;
edge1[tot1].cut = false ;
head1[u] = tot1 ++ ;
}
void Tarjan(int u , int pre)
{
int v ;
low[u] = dfn[u] = ++index ;
stack1[top ++] = u ;
instack[u] = true ;
int pre_cnt = 0 ;
for(int i = head[u] ; i != -1 ; i = edge[i].next)
{
v = edge[i].to ;
if(v == pre && pre_cnt == 0)
{
pre_cnt ++ ;
continue ;
}
if(!dfn[v])
{
Tarjan(v , u) ;
if(low[u] > low[v])
low[u] = low[v] ;
if(low[v] > dfn[u])
{
bridge ++ ;
edge[i].cut = true ;
edge[i ^ 1].cut = true ;
}
}
else if(instack[v] && low[u] > dfn[v])
low[u] = dfn[v] ;
}
if(low[u] == dfn[u])
{
block ++ ;
do
{
v = stack1[--top] ;
instack[v] = false ;
belong[v] = block ;
}
while(v != u) ;
}
}
void init()
{
tot = 0 ;
memset(head , -1 , sizeof(head)) ;
}
void init1()
{
tot1 = 0 ;
memset(head1 , -1 , sizeof(head1)) ;
memset(wei1 , 0 , sizeof(wei1)) ;
memset(vis , 0 , sizeof(vis)) ;
memset(vis1 , 0 , sizeof(vis1)) ;
memset(num , 0 , sizeof(num)) ;
memset(judge , 0 , sizeof(judge)) ;
}
int du[maxn] ;
void dfs(int u)
{
int i , j ;
int v ;
vis[u] = 1 ;
for(i = head[u] ; i != -1 ; i = edge[i].next)
{
v = edge[i].to ;
if(vis[v])
continue ;
if(belong[u] != belong[v])
{
addedge1(belong[u] , belong[v]) ;
addedge1(belong[v] , belong[u]) ;
}
dfs(v) ;
}
}
void draw(int n)
{
int i , j ;
init1() ;
for(i = 1 ; i <= n ; i ++)
{
wei1[belong[i]] += wei[i] ;
num[belong[i]] ++ ;
}
dfs(1) ;
}
bool dfs_delete(int u)
{
int i , j ;
int v ;
vis1[u] = 1 ;
if(num[u] > 1 || u == belong[s])
judge[u] = 1 ;
for(i = head1[u] ; i != -1 ; i = edge1[i].next)
{
v = edge1[i].to ;
if(vis1[v])
continue ;
judge[u] = max(judge[u] , dfs_delete(v)) ;
}
return judge[u] ;
}
void cal(int u , ll sum)
{
int i , j ;
int v ;
vis1[u] = 1 ;
if(judge[u])
two += wei1[u] ;
else
ans[u] = sum + wei1[u] ;
for(i = head1[u] ; i != -1 ; i = edge1[i].next)
{
v = edge1[i].to ;
if(vis1[v])
continue ;
if(judge[u])
cal(v , 0) ;
else
cal(v , sum + wei1[u]) ;
}
}
int main()
{
int n , m ;
int u , v ;
int i , j ;
ll max1 = 0 ;
scanf("%d%d" , &n , &m) ;
for(i = 1 ; i <= n ; i ++)
scanf("%lld" , &wei[i]) ;
init() ;
for(i = 1 ; i <= m ; i ++)
{
scanf("%d%d" , &u , &v) ;
addedge(u , v) ;
addedge(v , u) ;
}
scanf("%d" , &s) ;
Tarjan(s , s) ; //缩点
memset(vis1 , 0 , sizeof(vis1)) ;
draw(n) ; //新建一棵树
//把每个节点打上标记 如果从当前节点到叶子节点中间有不少于2个节点的
//双连通分量那就对当前节点的标记去掉
//去不掉的双连通分量就是有去无回的点 需取最大值
memset(vis1 , 0 , sizeof(vis1)) ;
dfs_delete(belong[s]) ;
memset(vis1 , 0 , sizeof(vis1)) ;
memset(ans , 0 , sizeof(ans)) ;
cal(belong[s] , ll(0)) ;
for(i = 1 ; i <= n ; i ++)
max1 = max(max1 , ans[i]) ;
max1 += two ;
printf("%lld" , max1) ;
}