bzoj1040 骑士
题意:共有n个骑士,每个骑士有一个战斗力w和他最憎恨的人,不能将他和他最憎恨的儿女同时派出去战斗,问能派出去的最大战斗力总和为多少?
解法:显然这是个求最大独立子集的题目。但是这并不是一个树,而是若干基环树和树组成的森林,为什么这么说。因为两个骑士相互憎恨,等价于一条边,如果存在这种情况,该连通分量就是一棵树,否则就是一个基环树(只有一个环的“树”)
基环树如何求最大独立集?
对环上某点进行讨论:
1)选取该点,那么顺序遍历该环,遍历到该点的前驱时,不能选取,因此只需要删除该前驱即可。
2)不选取该点,前驱可选,直接进行环上dp即可
但是在进行环上dp之前必须先对环上的每棵树进行树形dp求出选取根和不选根的最大权值和
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<stdlib.h>
#include<vector>
#include<set>
#include<map>
#include<queue>
#define ll long long
#define PLL pair<ll,ll>
#define MP make_pair
#define X first
#define Y second
using namespace std;
const ll maxn = 1000000+10;
ll n,m,head[maxn],tot;
struct node{
ll v,nxt;
}e[maxn*2];
ll vis[maxn],flag,vis2[maxn];
ll w[maxn];
ll dp[maxn][2];
ll has[maxn],cir[maxn],cnt;
ll nxt[maxn];
ll sumans,ans;
ll inq;
map<PLL,bool> ma;
void add(ll u,ll v){
e[tot].v=v,e[tot].nxt=head[u],head[u]=tot++;
}
void bianli(ll u,ll f){
vis2[u]=1;
for(ll i=head[u];i!=-1;i=e[i].nxt)
if(e[i].v!=f&&!vis2[e[i].v]) bianli(e[i].v,u);
}
void find_circle(ll u,ll f){//ÕÒµ½»·
vis[u]=1;
for(ll i=head[u];i!=-1;i=e[i].nxt){
ll v=e[i].v;
if(v==f) continue;
if(vis[v]) { inq=v,flag=v; return; }
find_circle(v,u);
if(flag>0){
if(flag==u) flag=-1;
return;
}
if(flag==-1) break;
}
vis[u]=0;
}
void dfs_circle(ll u,ll f){//±éÀú»·
if(has[u]) return;
cir[++cnt]=u;
has[u]=cnt;
for(ll i=head[u];i!=-1;i=e[i].nxt){
ll v=e[i].v;
if(v==f||!vis[v]) continue;
nxt[u]=v;
dfs_circle(v,u);
break;
}
}
void dfs(ll u,ll f){
ll tmp1=0,tmp0=0;
for(ll i=head[u];i!=-1;i=e[i].nxt){
ll v=e[i].v;
if(vis[v]||v==f) continue;
dfs(v,u);
tmp1+=dp[v][0],tmp0+=max(dp[v][0],dp[v][1]);
}
dp[u][1]=tmp1+w[u],dp[u][0]=tmp0;
}
PLL dfs1(ll u){
ll tmp1,tmp2;
if(nxt[u]!=cir[1]){
PLL a=dfs1(nxt[u]);
tmp1=dp[u][1]+a.Y;
tmp2=dp[u][0]+max(a.X,a.Y);
}
else tmp1=tmp2=dp[u][0];
return MP(tmp1,tmp2);
}
PLL dfs2(ll u){
ll tmp1,tmp2;
if(nxt[u]!=cir[1]){
PLL a=dfs2(nxt[u]);
tmp1=dp[u][1]+a.Y;
tmp2=dp[u][0]+max(a.X,a.Y);
}
else tmp1=dp[u][1],tmp2=dp[u][0];
return MP(tmp1,tmp2);
}
int main(){
//freopen("a.txt","r",stdin);
scanf("%lld",&n);
memset(head,-1,sizeof(head)); tot=0;
for(ll i=1;i<=n;i++){
ll v;
scanf("%lld%lld",&w[i],&v);
ll x=min(i,v),y=max(i,v);
if(ma.find(MP(x,y))!=ma.end()) continue;
ma[MP(x,y)]=1;
add(i,v),add(v,i);
}
for(ll l=1;l<=n;l++){
if(!vis2[l]){
inq=0,cnt=0,flag=0;
find_circle(l,0);
if(!inq){
dfs(l,0);
ans=max(dp[l][0],dp[l][1]);
sumans+=ans;
bianli(l,0);
continue;
}
dfs_circle(inq,0);
for(ll i=1;i<=cnt;i++){
ll u=cir[i],tmp0=0,tmp1=0;
for(ll j=head[u];j!=-1;j=e[j].nxt){
ll v=e[j].v;
if(!vis[v]) dfs(v,u),tmp0+=max(dp[v][1],dp[v][0]),tmp1+=dp[v][0];
}
dp[u][1]=tmp1+w[u],dp[u][0]=tmp0;
}
PLL a=dfs1(cir[1]);
ans=max(a.X,a.Y);
a=dfs2(cir[1]);
ans=max(ans,a.Y);
sumans+=ans;
bianli(l,0);
}
}
printf("%lld\n",sumans);
return 0;
}
bzoj2878 迷失游乐园
题意:n个点的无向连通图,边数只能是n/n-1。从任一点出发等可能遍历相邻未访问节点,问最终形成的路径期望长度是多少?
解法:基环树上的概率dp
1)先解决以每个环点为根的树形dp。
先规定方向:环点定义为根节点,树中深度越深,定义为向下;深度越浅,定义为向上。沿环的方向走也定义为向上
down[u]u向下行走的期望长度
down[fa]=∑u是fa儿子(down[u]+<u,fa>)/son[fa]
这个不需要沿着环方向走,因此无需环形dp,可以先求出来。
up[u]表示树上某点向上行走的长度,需要先求出根节点的up值。
求根节点的up值即从某点开始顺向和逆向走一遍环,每次有两种选择,向下走(进入树)/向上走(沿环走),这个可以通过树形dp求出从某点出发的向上走期望长度,总复杂度为O(k^2),k为环上节点个数。
然后就可以在树上进行dp了。
up[u]=<u,fa>+(up[fa]∗numfa[fa]+down[fa]∗numson[fa]−down[u]−<u,fa>)/(numson[fa]+numfa[fa]−1)
如果numson[fa]+numfa[fa]==1,那么后面的一项舍去(即fa除了该儿子u没有其他延伸方向)
环上的点numfa(父节点个数)为2,其余节点numfa为1
numson为树上向下走的方向数
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<stdlib.h>
#include<vector>
#include<set>
#include<queue>
#define ll long long
#define PII pair<int,int>
#define MP make_pair
using namespace std;
const int maxn = 100000+10;
int n,m,head[maxn],tot;
struct node{
int v,nxt,w;
}e[maxn<<2];
int vis[maxn],flag;
double down[maxn],up[maxn];
int fa[maxn],son[maxn];
int has[maxn],cir[maxn],cnt;
int nxt[maxn],pre[maxn];
int len[50][50];
double ans;
void add(int u,int v,int w){
e[tot].v=v,e[tot].nxt=head[u],e[tot].w=w,head[u]=tot++;
}
void find_circle(int u,int f){//找到环
vis[u]=1;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v;
if(v==f) continue;
if(vis[v]) { flag=v; return; }
find_circle(v,u);
if(flag>0){
if(flag==u) flag=-1;
return;
}
if(flag==-1) break;
}
vis[u]=0;
}
void dfs_circle(int u,int f){//遍历环
if(has[u]) return;
cir[++cnt]=u;
has[u]=cnt;
fa[u]=2;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(v==f||!vis[v]) continue;
pre[v]=u,nxt[u]=v;
dfs_circle(v,u);
len[has[u]][has[v]]=len[has[v]][has[u]]=w;
break;
}
}
void dfs_down(int u,int f){
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(v==f||vis[v]) continue;
fa[v]=1;
dfs_down(v,u);
son[u]++;
down[u]+=(down[v]+w);
}
if(son[u]) down[u]/=son[u];
}
void dfs_up(int u,int f,int w){
up[u]=w;
if(fa[f]+son[f]>1) up[u]+=((up[f]*fa[f]+down[f]*son[f]-down[u]-w)/(fa[f]+son[f]-1.0));
// printf("%d %d %.2lf\n",u,f,fa[f]+son[f]-1.0);
for(int i=head[u];i!=-1;i=e[i].nxt)
if(e[i].v!=f) dfs_up(e[i].v,u,e[i].w);
}
int main(){
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head)); tot=0;
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
find_circle(1,0);
if(m<n){
dfs_down(1,0);
for(int i=head[1];i!=-1;i=e[i].nxt){
dfs_up(e[i].v,1,e[i].w);
}
}
else{
for(int i=1;i<=n;i++){
if(vis[i]){
dfs_circle(i,0);
break;
}
}
for(int i=1;i<=cnt;i++) dfs_down(cir[i],0);
for(int i=1;i<=cnt;i++){
int u=cir[i];
double k=1.0;
for(int j=nxt[u];j!=u;j=nxt[j]){
if(nxt[j]!=u) up[u]+=k*(len[has[pre[j]]][has[j]]+down[j]*son[j]/(son[j]+1.0));
else up[u]+=k*(len[has[pre[j]]][has[j]]+down[j]);
k/=(son[j]+1);
}
k=1.0;
for(int j=pre[u];j!=u;j=pre[j]){
if(pre[j]!=u) up[u]+=k*(len[has[nxt[j]]][has[j]]+down[j]*son[j]/(son[j]+1.0));
else up[u]+=k*(len[has[nxt[j]]][has[j]]+down[j]);
k/=(son[j]+1);
}
up[u]/=2.0;
}
for(int i=1;i<=cnt;i++){
int u=cir[i];
for(int j=head[u];j!=-1;j=e[j].nxt){
int v=e[j].v,w=e[j].w;
if(!has[v]) dfs_up(v,u,w);
}
}
}
for(int i=1;i<=n;i++){
ans+=(down[i]*son[i]+up[i]*fa[i])/(double)(son[i]+fa[i]);
}
printf("%.5lf\n",ans/n);
return 0;
}
bzoj1023 cactus仙人掌图
题意:定义仙人掌图是每条边最多在一个简单环中的无向连通图。仙人掌图中的直径为任意两点最短路径的最大值。求仙人掌图的直径
解法:
1)先求仙人掌图的dfs生成树
这里需要知道一个结论:dfs生成树的非树边只能从u连向它的祖先
因此每个环在dfs树中一定存在一个最高点
2)定义F[u]:u到以u为根的子树任意一点的最大距离。边集不仅包含树边,还包含最高点属于u的子树的非树边,但是不包括同一环上的边。
对于非环上最高点:
F[u]=max(F[v]+1),v是u的儿子,u与v不属于同一环
如果直径不包含环边:
ans=max(ans,u儿子的最大和次大F值+2)
如果直径包含环边
ans=max(ans,F[i]+F[j]+dis(i,j)),i和j属于同一环
这个式子需要用到增倍和单调队列优化两个技巧。
dis(i,j)=min(size,size-(j-i)),为了去掉min符号,需要将元素增倍,然后线性处理:对于每个i求比他小不大于size/2的区间内求F[j]-j的最小值——滑动窗口求最值问题,可以用单调队列优化。
对于每个环的最高点u,在更新完u所在子树的F值和ans值后,F[u]的求法发生变化,需要添加包含环边的路径:
F[u]=max(F[u],dis(u,i)+F[i]),i和u属于同一个环
将所有的F值和ans值更新完毕后,即可得到正确结果。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<cmath>
#include<algorithm>
#include<stdlib.h>
#include<vector>
#include<set>
#include<map>
#include<queue>
#define ll long long
#define PLL pair<ll,ll>
#define MP make_pair
#define X first
#define Y second
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int maxn = 50000+10;
const int maxm = 20000000+10;
int n,m;
struct node{
int v,nxt;
}e[maxm];
int head[maxn],tot;
void add(int u,int v){ e[++tot].v=v,e[tot].nxt=head[u],head[u]=tot; }
int f[maxn];
int fa[maxn],dep[maxn],dfn[maxn],low[maxn],dfs_clock;
int a[2*maxn],q[2*maxn];
int ans;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void dp(int root,int u){
int siz=dep[u]-dep[root]+1;
int tmp=siz;
for(int i=u;i!=root;i=fa[i]) a[tmp--]=f[i];
a[tmp]=f[root];
for(int i=siz+1;i<=2*siz;i++) a[i]=a[i-siz];
int top=1,tail=1; q[1]=1;
int len=siz/2;
for(int i=2;i<=2*siz;i++){
while(top<=tail&&i-q[top]>len) top++;
ans=max(ans,a[i]+i+a[q[top]]-q[top]);
while(top<=tail&&a[q[tail]]-q[tail]<=a[i]-i) tail--;
q[++tail]=i;
}
for(int i=2;i<=siz;i++) f[root]=max(f[root],a[i]+min(i-1,siz-(i-1)));
}
void tarjan(int u){
low[u]=dfn[u]=++dfs_clock;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(fa[u]==v) continue;
if(!dfn[v]){
fa[v]=u,dep[v]=dep[u]+1;
tarjan(v);
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
if(low[v]>dfn[u]){//u和v不在同一个环上
ans=max(ans,f[u]+f[v]+1);
f[u]=max(f[u],f[v]+1);
}
}
for(int i=head[u];i;i=e[i].nxt){//如果有环,那么u是最高点,v是最低点
int v=e[i].v;
if(fa[v]!=u&&dfn[v]>dfn[u]) dp(u,v);//与u相连&&dfn比u大&&不是u的儿子=>是u的环上最低点
}
}
int main(){
//freopen("a.txt","r",stdin);
n=read(),m=read();
while(m--){
int a,b,k;
k=read(); a=read(); k--;
while(k--){
b=read();
add(a,b),add(b,a);a=b;
}
}
tarjan(1);
printf("%d\n",ans);
return 0;
}