2020.02.09日常总结——树上的动态规划与树上的贪心

[ P O I 2014 ] F A R F a r m C r a f t \color{green}{[POI2014]FAR-FarmCraft}

\color{blue}{【题目大意】:} (翻译来自洛谷,侵权必删)

在这里插入图片描述
\color{blue}{【思路】:} s i z e u size_u 表示遍历 u u 的子树所需要的总时间, f u f_u 表示遍历 u u 的子树且 u u 的子树都装好了农场物语所需要的时间。

我们发现 f u s i z e u f_u-size_u 这么多的时间我们的管理员是浪费了的(如果他一直等着的话),所以为了节约时间,我们必然要对它加以利用。

所以,我们按 f u s i z e u f_u-size_u 从大到小的顺序遍历求解即可。当然按 f u s i z e u f_u-size_u 从大到小排序相当于按 s i z e u f u size_u-f_u 从小到大排序。

\color{blue}{【代码】:}

//g即思路中的size,profit表示一个点装好电脑的时间 
int f[N],ans,n,profit[N],g[N];
vector<pair<int,int> >G[N];
void dfs(int u,int fa){
	if (u!=1) f[u]=profit[u];
	for(int i=h[u];i;i=e[i].next)
		if (e[i].to!=fa) dfs(e[i].to,u);
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		if (to==fa) continue;
		G[u].push_back(make_pair(g[to]-f[to],to));
	}
	sort(G[u].begin(),G[u].end());
	for(int i=0;i<(int)G[u].size();i++){
		register int to=G[u][i].second;
		f[u]=max(f[u],f[to]+g[u]+1);
		g[u]+=g[to]+2;//注意还有回来 
	}
}
int main(){
	n=read();f[1]=ans=-1;
	for(int i=1;i<=n;i++)
		profit[i]=read();
	for(int i=1;i<n;i++)
		add(read(),read());
	dfs(1,-1);ans=f[1];//注意因为管理员最后才给自己安装,所以根特殊计算 
	ans=max(ans,g[1]+profit[1]);
	printf("%d",ans);
	return 0;
}

************************
状态:Accepted
分数:100分
语言:c++
************************

\color{blue}{【时间复杂度计算】:} 本题看似排序了很多次,时间复杂度可能达到了 O ( n 2 × log n ) O(n^2 \times \log n) 。其实不然,因为所有点的儿子数总和才是 n n ,所以时间复杂度最多为 O ( n × log n ) O(n \times \log n)


B Z O J 4472 \color{green}{BZOJ4472}

\color{blue}{【题意】:} 某售货员小T要到若干城镇去推销商品,由于该地区是交通不便的山区,任意两个城镇之间都只有 \color{red}{唯一的} 可能经过其它城镇的路线。 小T可以准确地估计出在每个城镇停留的净收益。这些净收益可能是负数,即推销商品的利润抵不上花费。由于交通不便,小T经过每个城镇都需要停留,在每个城镇的停留次数与在该地的净收益无关,因为很多费用不是计次收取的,而每个城镇对小T的商品需求也是相对固定的, \color{orange}{停留一次后就饱和了} 。每个城镇为了强化治安,对外地人的最多停留次数有严格的规定。请你帮小T设计一个收益最大的巡回方案,即从家乡出发,在经过的每个城镇停留,最后回到家乡的旅行方案。你的程序只需输出最大收益,以及最优方案是否唯一。方案并不包括路线的细节,方案相同的标准是选择经过并停留的城镇是否相同。因为取消巡回也是一种方案,因此最大收益不会是负数小T 在家乡净收益是零,因为在家乡是本地人,家乡对小T当然没有停留次数的限制。

\color{blue}{【思路】:} f u f_u 表示从 u u 出发遍历 u u 的子树最后返回 u u 所能获得的最大收益。因为次数有限制,所以理所当然地是按 f u f_u 从大到小遍历子树,直到用尽限制的次数为止(当然,如果收益为正的子树个数小于限制可走次数,那就宁可浪费啦)。

需要注意的是,不走的方案我们最后才考虑。而且记 l i m i t u limit_u 表示可以访问 u u 的次数的限制,则只能遍历 l i m i t u 1 limit_u-1 棵子树,因为从根到 u u 也用了一次限制。

\color{blue}{【代码】:}

**************************************************************
    Problem: 4472
    Language: C++
    Result: 正确
    Time:404 ms
    Memory:7172 kb
****************************************************************
 
const int N=1e5+100;
typedef long long ll;
int limit[N],profit[N],n;
ll dp[N];vector<ll> v[N];
struct node{
    int next,to;
}e[N<<1];int h[N],tot;
inline void add(int a,int b){
    e[++tot]=(node){h[a],b};h[a]=tot;
    e[++tot]=(node){h[b],a};h[b]=tot;
}
inline bool cmp(ll a,ll b){
    return a>b;
}
bool flag=true;//flag:方案是否唯一 
void calc_answer(int u,int fa){
    dp[u]=profit[u];int i;
    for(i=h[u];i;i=e[i].next){
        register int to=e[i].to;
        if (to==fa) continue;
        calc_answer(to,u);
        v[u].push_back(dp[to]);
    }
    int t=min(limit[u]-1,(int)v[u].size());
    sort(v[u].begin(),v[u].end(),cmp);
    for(register int i=0;i<t;i++){
        dp[u]+=max(v[u][i],0ll);
        if (v[u][i]==0ll) flag=false;
    }
    if (t<(int)v[u].size()&&v[u][t+1]==v[u][t]) flag=false;
}
int main(){
//  freopen("t1.in","r",stdin);
    n=read();limit[1]=n+1;
    for(int i=2;i<=n;i++)
        profit[i]=read();
    for(int i=2;i<=n;i++)
        limit[i]=read();
    for(int i=2;i<=n;i++)
        add(read(),read());
    calc_answer(1,-1);
    printf("%lld\n",max(0ll,dp[1]));
    if (dp[1]<=0ll) printf("solution is unique");
    else if (flag) printf("solution is unique");
    else printf("solution is not unique");
    return 0;
}
发布了103 篇原创文章 · 获赞 4 · 访问量 6731

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/104222363
今日推荐