仙人掌基础题,结果一脸懵逼。。。
%PB大佬,瞬间讲懂这个dp(@ο@)
目录
题目
BZOJ-1023 [SHOI2008]cactus仙人掌图
大意就是求一个仙人掌图的直径,有点像TOJ3517,那个是求树的直径,而这个是仙人掌,有环。。。
思路
其实,如果没有环,那么直接dp[u]=max(dp[v]+1);
所以,我们考虑把环单独拿出来处理;
- 联想tarjan求SCC的做法,我们dfs时如果当
dfn[u]<dfn[v] && fa[v]!=u
,那么这一定是在环里转了一圈,又回到环顶了; - 既然找到了环,我们把它摘出来单独处理就好了;
- 在环中,dis(i,j)=min(j-i,n-i+j),所以我们直接用单调队列,队列里最多存n/2个点,而且队首最优;
- 对于环上某个点u,它能参与构成的最长链=dp[u]+dp[q[head]]+dis(u,q[head]),其中dis(u,q[head])在这个队列中=u-q[head],所以dp[u]+u是已固定了,所以队首最优就是dp[head]-q[head];
- 由于更新ans时,只转一圈可能会漏掉后1/4圈加前1/4圈倒着连成的最长链,所以要倍长这个环,转两圈;
代码
#include <cstdio>
#include <iostream>
using namespace std;
#define min(a,b) ((a)^(((a)^(b))&(-((a)>(b)))))
#define max(a,b) ((a)^(((a)^(b))&(-((a)<(b)))))
inline int read();
const int MAXN=50005;
int he[MAXN],cnt,n,m,index;
int dfn[MAXN],low[MAXN],de[MAXN];
int dp[MAXN]; //dp[i]表示以i为根的子树且i为一个端点的树边最长链
int tmp[MAXN<<1],fa[MAXN];
int q[MAXN<<1],head,tail;
int ans; //即直径
struct line{
int to,nex;
}ed[MAXN<<2];
inline void add(const int &u,const int &v){
ed[++cnt].nex=he[u];
he[u]=cnt;
ed[cnt].to=v;
}
inline void cir_dp(const int &rt,const int &x){
int tot=de[x]-de[rt]+1;
//由于dfs,这也就是环里点的个数
for(int i=x;i!=rt;i=fa[i],--tot)
tmp[tot]=dp[i];
//tmp[i]是rt为最高点的环里的第i号点连向环外的最长链的长度
tmp[tot]=dp[rt];
tot=de[x]-de[rt]+1;
//倍长这个环,
//转两圈才能保证最开始的点的dp[]或tmp[]更新到最优
for(int i=1;i<=tot;++i)
tmp[i+tot]=tmp[i];
//单调队列。。。
q[1]=1; head=tail=1;
for(int i=2;i<=(tot<<1);++i){
//由于环中dis(i,j)=min(j-i,n-j+i),
//所以干脆队列设为半个环长就好了
while(head<=tail && i-q[head]>(tot>>1))
++head;
//对于环中的每个点i,
//它的实际最长链=以它为端点的树边最长链dp[i]或tmp[i]+它到环顶的距离
ans=max(ans,tmp[i]+i+tmp[q[head]]-q[head]);
//为保证队首最优,对于i,用它更新完ans后,
//它对下一个点i'的贡献便是tmp[i]-i,顾有一下的出队条件
while(head<=tail && tmp[q[tail]]-q[tail]<=tmp[i]-i)
--tail;
//i入队
q[++tail]=i;
}
for(int i=2;i<=tot;++i) //把环里的最优解更新到环顶,于是环就无用了
dp[rt]=max(dp[rt],tmp[i]+min(i-1,tot-i+1));
}
void dfs(const int &u){
dfn[u]=low[u]=++index;
for(int i=he[u],v;i;i=ed[i].nex){
v=ed[i].to;
if(v!=fa[u]){
//没有访问过v
if(!dfn[v]){
fa[v]=u;
de[v]=de[u]+1;
dfs(v);
low[u]=min(low[u],low[v]);
}
//类似tarjan,low[i]即为i所在连通块的min(dfn)
else low[u]=min(low[u],dfn[v]);
if(dfn[u]<low[v]){
//u,v不在同一连通块,
//即边(u,v)是树边,按树形dp跑即可
ans=max(ans,dp[u]+dp[v]+1);
dp[u]=max(dp[u],dp[v]+1);
}
}
}
for(int i=he[u],v;i;i=ed[i].nex){
v=ed[i].to;
//这种情况是u是环顶,dfs在环里转一圈到v了,
if(fa[v]!=u && dfn[u]<dfn[v])
cir_dp(u,v); //专门处理这个环
}
}
int main(){
n=read(),m=read();
for(int i=1,k,u;i<=m;++i){
k=read(),u=read();
for(int j=1,v;j<k;++j){
v=read();
add(u,v),add(v,u);
u=v;
}
}
dfs(1);
printf("%d\n",ans);
return 0;
}
inline int read(){
char c; int x;
while(c=getchar(),c<'0' || '9'<c);
x=c-'0';
while(c=getchar(),'0'<=c && c<='9')
x=x*10+c-'0';
return x;
}