JLOI2015/BZOJ4003 城池攻占

本文探讨了一款桌游中骑士攻占城池的策略算法,通过倍增操作优化了可并堆方法,实现了对骑士战斗力及城池占领情况的有效追踪。

Description

  小铭铭最近获得了一副新的桌游,游戏中需要用m个骑士攻占n个城池。
  这n个城池用1到n的整数表示。除1号城池外,城池i会受到另一座城池fi的管辖,其中fi  每个城池有一个防御值hi,如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击管辖这座城池的城池,直到占领1号城池,或牺牲为止。
  除1号城池外,每个城池i会给出一个战斗力变化参数ai,vi。若ai=0,攻占城池i以后骑士战斗力会增加vi;若ai=1,攻占城池i以后,战斗力会乘以vi。
  注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果。
  现在的问题是,对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。

Input

  第1行包含两个正整数n,m,表示城池的数量和骑士的数量。
  第2行包含n个整数,其中第i个数为hi,表示城池i的防御值。
  第3到n+1行,每行包含三个整数。其中第i+1行的三个数为fi,ai,vi,分别表示管辖这座城池的城池编号和两个战斗力变化参数。
  第n+2到n+m+1行,每行包含两个整数。其中第n+i行的两个数为si,ci,分别表示初始战斗力和第一个攻击的城池。

Output

  输出n+m行,每行包含一个非负整数。其中前n行分别表示在城池1到n牺牲的骑士数量,后m行分别表示骑士1到m攻占的城池数量。

Sample Input

5 5 50 20 10 10 30 1 1 2 2 0 5 2 0 -10 1 0 10 20 2 10 3 40 4 20 4 35 5

Sample Output

2 2 0 0 0 1 1 3 1 1

Hint

【数据范围】
  对于20%的数据,0 < n,m <= 3000;
  对于20%的数据,保证对于所有的城池i,fi=i-1。
  对于20%的数据,保证对于所有的城池i,ai=0,vi=0。
  以上三类数据两两没有交集。
  对于100%的数据,1<=n,m<=300000,1<=fi;当ai=1时,vi>0;保证任何时候骑士战斗力值的绝对值不超过10^18。

网上全是可并堆写法,虽然我才写可并堆没多久,但是已经忘完了,而且感觉有点不好搞(网上的标好像很长)。于是我们来倍增操作一下。

f[i][j]表示从i开始攻略2^j个城池需要的最小攻击力,辅助数组g[i][j]维护两个标记sum和mul(类似于行星序列那道题的标记)。所以我们有f[i][j]=max(f[i][j-1],(f[fa[i][j-1][j-1]-sum)/mul)。

#include<bits/stdc++.h>
using namespace std;
const int Maxn=300005;
#define ll long long
vector<int>G[Maxn];
int N[Maxn],M[Maxn];
int n,m,p[25][Maxn];
ll a[Maxn],v[Maxn],mn[25][Maxn];
struct info{
	ll tag,mul;
	info(ll _tag=0,ll _mul=1){tag=_tag,mul=_mul;}
	info operator*(const info&rhs){return info(tag*rhs.mul+rhs.tag,mul*rhs.mul);}
}g[25][Maxn];
void dfs(int x){
	int maxlog=log2(n);
	g[0][x]=info(a[x]?0ll:v[x],a[x]?v[x]:1ll);
	for(int j=1;j<=maxlog;++j)if(~p[j-1][x]){
		p[j][x]=p[j-1][p[j-1][x]];
		g[j][x]=g[j-1][x]*g[j-1][p[j-1][x]];
		mn[j][x]=max(mn[j-1][x],(ll)ceil((mn[j-1][p[j-1][x]]-g[j-1][x].tag)/(1.0*g[j-1][x].mul)));
	}
	for(int i=0;i<G[x].size();++i)
		if(G[x][i]^p[0][x])dfs(G[x][i]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%lld",&mn[0][i]);
	}
	memset(p,-1,sizeof(p));
	for(int i=2;i<=n;++i){
		scanf("%d%lld%lld",&p[0][i],&a[i],&v[i]);
		G[p[0][i]].push_back(i);
	}
	dfs(1);
	int maxlog=log2(n);
	for(int i=1;i<=m;++i){
		ll v;int x,ret=0;scanf("%lld%d",&v,&x);
		for(int j=maxlog;j>=0;--j)if(~p[j][x]&&v>=mn[j][x]){
			v=v*g[j][x].mul+g[j][x].tag;
			M[i]+=(1<<j);
			x=p[j][x];
		}
		if(~x&&v>=mn[0][x])++M[i];
		if(~x&&v<mn[0][x])++N[x];
	}
	for(int i=1;i<=n;++i)
		cout<<N[i]<<'\n';
	for(int i=1;i<=m;++i)
		cout<<M[i]<<'\n';
	return 0;
}

 

### NOIP2015 运输计划 BZOJ4326 题解分析 #### 问题背景 该问题是经典的图论优化问题之一,主要考察树结构上的路径操作以及高效的数据处理能力。题目要求在一个由 $n$ 个节点组成的无向连通树中找到最优的一条边将其改造为虫洞(通过此边不需要耗费时间),从而使得给定的 $m$ 条运输路径中的最长耗时最小化。 --- #### 解决方案概述 解决这一问题的核心在于利用 **二分答案** **树上差分技术** 的组合来实现高效的计算过程。以下是具体的技术细节: 1. **二分答案**: 设当前目标是最小化的最大路径长度为 $T_{\text{max}}$。我们可以通过二分的方式逐步逼近最终的结果。每次尝试验证是否存在一种方式将某条边改为虫洞后使所有路径的最大值不超过当前设定的目标值 $mid$[^1]。 2. **路径标记与统计**: 使用树上差分的思想对每一条路径进行标记并快速统计受影响的情况。假设点之间的最近公共祖先 (Lowest Common Ancestor, LCA) 是 $r = \text{lca}(u_i, v_i)$,则可以在三个位置分别施加影响:增加 $(u_i + 1), (v_i + 1)$ 同时减少 $(r - 2)$。这种操作能够有效覆盖整条路径的影响范围,并便于后续统一查询判断[^1]。 3. **数据结构支持**: 结合线段树或者 BIT (Binary Indexed Tree),可以进一步加速区间修改单点查询的操作效率。这些工具帮助我们在复杂度范围内完成大量路径的同时更新检索需求[^2]。 4. **实际编码技巧**: 实现过程中需要注意一些边界条件技术要点: - 正确维护 DFS 序列以便映射原树节点到连续编号序列; - 准备好辅助函数用于快速定位 LCA 节点及其对应关系; - 编码阶段应特别留意变量初始化顺序及循环终止逻辑以防潜在错误发生。 下面给出一段基于上述原理的具体 Python 实现代码作为参考: ```python from collections import defaultdict, deque class Solution: def __init__(self, n, edges): self.n = n self.graph = defaultdict(list) for u, v, w in edges: self.graph[u].append((v, w)) self.graph[v].append((u, w)) def preprocess(self): """Preprocess the tree to get dfs order and lca.""" pass def binary_search_answer(self, paths): low, high = 0, int(1e9) best_possible_time = high while low <= high: mid = (low + high) // 2 if self.check(mid, paths): # Check feasibility with current 'mid' best_possible_time = min(best_possible_time, mid) high = mid - 1 else: low = mid + 1 return best_possible_time def check(self, limit, paths): diff_array = [0]*(self.n+1) for path_start, path_end in paths: r = self.lca(path_start, path_end) # Apply difference on nodes based on their relationship. diff_array[path_start] += 1 diff_array[path_end] += 1 diff_array[r] -= 2 suffix_sum = [sum(diff_array[:i]) for i in range(len(diff_array)+1)] # Verify whether any edge can be modified within given constraints. possible_to_reduce_max = False for node in range(1, self.n+1): parent_node = self.parent[node] if suffix_sum[node]-suffix_sum[parent_node]>limit: continue elif not possible_to_reduce_max: possible_to_reduce_max=True return possible_to_reduce_max # Example usage of class methods would follow here... ``` --- #### 总结说明 综上所述,本题的关键突破点在于如何巧妙运用二分策略缩小搜索空间,再辅以恰当的树形结构遍历技术差分手段提升整体性能表现。这种方法不仅适用于此类特定场景下的最优化求解任务,在更广泛的动态规划领域也有着广泛的应用前景[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值