• 题解 P1453 【城市环路】


    P1453 城市环路

    感觉基环树(or环套树)的题目一般都是找到树上的环,断掉一条边再进行树上的操作(如noip2018P5022 旅行

    双倍经验:P2607 [ZJOI2008]骑士

    P1453和P2607这两题实际上就是基环树上的P1352 没有上司的舞会,用树形DP即可,但是因为有环,所以要先找到环再删一条边才能DP

    至于树形DP就不多说了,毕竟是基环树,最主要的就是找环操作了,翻了翻题解,大概有下面几种方法:

    一. 读入时判断

    1. 并查集 ,若两点在一个并查集中则记录下来,不连边不合并

    2. (不建议使用)bool记录点是否出现过,若读入时两点在之前都已出现了,则为环,不连边(BUG:本题数据较弱可过,但可能在一些数据中出错,看评论中有一组hack数据:5 1 2 3 4 5 0 1 2 3 2 4 0 2 3 4 9.0,答案为63.0,但这种找环方法会超时)

    二、读入后判断

    1. 如本程序,dfs找环,找到时记录两点a,b或边编号e,dp时不仅要判断v!=fa,还应判断(u!=a || v!=b) && (u!=b || v!=a)或(e!=i && (e^1)!=i)(双向边)

    2. (不建议使用)连单向边u->v(注意要保证有有向环),记录fa[v]=u,从一个已存在的点开始不断找fa直到遇到走过的则找到了环。这种写法可以用在P2607(见这个题解),此题未必可行

    while(!vis[fa[rt]]) rt=fa[rt],vis[rt]=1;//rt与fa[rt]连为了环
    

    对于此题,找到环上两点a,b后,分别以a,b为根做两次树形DP,第一次取f[a][0],第二次取f[b][0](这样保证a与b虽相邻但不会同时被被取),取max(f[a][0],f[b][0])与K相乘即可

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    #define ll int //这个代码是在P2607的基础上修改的,所以直接把ll定义为int了
    const int N=1e5+100;
    int h[N],rt,rt2,n,cnt,val[N];
    bool vis[N];
    ll f[N][2],ans;
    struct edge {
    	int v,h;
    } e[N<<1];
    inline void add(int u,int v) {
    	e[++cnt]=(edge) {
    		v,h[u]
    	},h[u]=cnt;
    }
    
    void dp(int x,int fa) {
    	vis[x]=1,f[x][1]=val[x];
    	for (int i=h[x],v; i; i=e[i].h) {
    		v=e[i].v;
    		if ((x!=rt || v!=rt2) && (x!=rt2 || v!=rt) && v!=fa)
    			dp(v,x),
    			f[x][0]+=max(f[v][0],f[v][1]),
    			         f[x][1]+=f[v][0];
    	}
    }
    
    bool find(int x,int fa) {  //dfs找环
    	vis[x]=1;
    	for(int i=h[x]; i; i=e[i].h)
    		if (e[i].v^fa)
    			if (vis[e[i].v]) {
    				rt=e[i].v,rt2=x; //找到了环,记录起端点(相当于切断i这条边)
    				return 1;
    			} else if (find(e[i].v,x)) return 1;
    }
    
    void solve(int x) { //分别以两端点做树形DP
    	find(x,-1);
    	dp(rt,rt);
    	ll tmp=f[rt][0];
        
    	memset(f,0,sizeof f);
    	dp(rt2,rt2);
        
    	ans=max(tmp,f[rt2][0]);
    }
    int main() {
    	scanf("%d",&n);
    	for (int i=0; i<n; i++) scanf("%d",&val[i]);
    	for (int i=1,x,y; i<=n; i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    	solve(0); //注意若是森林需枚举1~n的点
    	double tmp;
    	scanf("%lf",&tmp);
    	printf("%.1lf",(double)ans*tmp);
    }
    
  • 相关阅读:
    监听 view 初始化时
    动画 帧动画
    Android Studio 修改jar包中的.class代码
    ScrollView 滚动条
    SpannableString 设置文字中的颜色 链接
    onActivityResult 解耦 不需要一层一层的写
    滚动 Scroller OverScroller
    startActivity 新开一个Activity
    Fragment java.lang.IllegalStateException 生命周期
    jenkins 基于docker部署springboot项目详细步骤
  • 原文地址:https://www.cnblogs.com/Randolph68706/p/11845463.html
Copyright © 2020-2023  润新知