CSP-S T4-Tree 求调(详细思路)
  • 板块学术版
  • 楼主Twiter_ln
  • 当前回复1
  • 已保存回复1
  • 发布时间2023/10/24 15:38
  • 上次更新2023/11/2 11:35:50
查看原帖
CSP-S T4-Tree 求调(详细思路)
188970
Twiter_ln楼主2023/10/24 15:38

个人思路分为几个部分:


  1. 二分总答案

( deep[i]deep[i] 随手验证下)

  1. 对于每个答案x记性判断,判断步骤↓↓:(采用解方程)

    (1) b+x∗c<=a?b+x*c <= a? 减掉后面1的部分 解方程

    (2) 直接解方程 计算 左端点:最迟种植天数 day[i]day[i]

    ps:c[i]<0ps: c[i]<0 但若第 xx 天 未使得 当日生长高度 <1<1 ,则对 等差数列计算答案不影响。


  1. 一元二次方程:(只考虑等差数列部分)

    设开始天数为f(first)f(first), 传参 a,b,c,enda, b, c, end

    则有 [(b+f∗c)+(b+e∗c)]∗(e−f+1)/2>=a[(b+f*c)+(b+e*c)]*(e-f+1)/2 >=a ,求解 ff 。

    那么求解f最小值,把原式变为 ∗∗∗−2∗a=0***-2*a = 0 ,求解 ff 并向下取整,判断有无解

    ff 最后有2解, 但必有一根无解 oror 2根都无解(貌似);最后验证 day[i]day[i] 。


  1. 树上 范围向上传递,压缩区间;最后构造序列,判断该解是否可行


#include<iostream> // 4 tree
#include<cstdio> // 1e5
#include<cmath>
#include<cstring>
#include<algorithm>
#define lli long long
#define test cout<<"Test"

using namespace std;

const int N = 100037;
lli a[N], b[N], c[N];
int n, x, y;

struct pot{
	int v, nxt;
}E[N<<1];
int head[N], tot = 0;
void Add_edge(int u, int v) {
	E[++tot].v = v;
	E[tot].nxt = head[u];
	head[u] = tot;
}

int dep[N], vis[N];
inline void dfs(int u) {
	for(int i=head[u];i;i=E[i].nxt) {
		int v = E[i].v;
		if(vis[v]) continue;
		vis[v] = 1;
		dep[v] = dep[u] + 1;
		dfs(v);
	}
}
int day[N]; // 对于每个点和总天数x,最迟种植第i棵树的时间
inline void dfs2(int u) {
//	cout<<"U: "<<u<<"\n";
	for(int i=head[u];i;i=E[i].nxt) {
		int v = E[i].v;
		if(vis[v]) continue;
		vis[v] = 1;
		dfs2(v);
		day[u] = min(day[u], day[v]-1);
		// 向父亲压缩范围
	}
	
}

inline bool pd(lli ass, lli fff, lli eend, lli a) {
	if(eend<ass || ass<=0 || fff<2*a) return false;
	else return true;
	// 判断ans 是否可行
}
inline lli fcc(lli b, lli c, lli a, lli e) {
	lli aa, bb, cc;
	aa = -c;
	bb = c-2*b;
	cc = (e+1) * (2*b + e*c) - 2*a;
	// 方程打开后 对于f的 2次项 1次项 常数项 的系数
  
	lli derta = bb*bb - 4*aa*cc;
	// △
  
	double ans1, ans2; // 方程2根
	lli fl1, fl2, yz1, yz2;
	ans1 = (double) ( -bb + 1.0* sqrt(derta) ) / (2.0*aa);
	ans2 = (double) ( -bb - 1.0* sqrt(derta) ) / (2.0*aa);
	fl1 = floor(ans1);
	fl2 = floor(ans2);// 向下取整后结果
  
	yz1 = (b+e*c + b+fl1*c)*(e-fl1+1);
	yz2 = (b+e*c + b+fl2*c)*(e-fl2+1);
  // 带回验证区间和
	
	if(!pd(fl1, yz1, e, a) && !pd(fl2, yz2, e, a)) return -1; // 无解
	else {
		if(pd(fl1, yz1, e, a)) return floor(ans1);
		if(pd(fl1, yz1, e, a)) return floor(ans2);
	}
	return -1;
}


inline bool check(int x) { // 二分总答案
	memset(day, 0, sizeof(day));
//	cout<<"X: "<<x<<"; "<<endl;
	int len1;
	for(int i=1;i<=n;++i) {
		if(dep[i] > x) return false;
		
		if(c[i] == 0) { // 直接倒退计算,无需等差数列
			day[i] = x-ceil(a[i]*1.0 / b[i])+1;
			continue;
		}
		
		
		if(b[i]+c[i]*x <= 0) { // c[i] < 0 -->  (<1)
			
			len1 = ceil(1.0*(b[i]+c[i])/(-c[i]*1.0)); // 左区间 >1 的天数
			if(a[i] <= x-len1) { // 右部分生长高度为1的总长度 都足够
				day[i] = len1 + a[i];
				continue;
			}
			
			day[i] = fcc(b[i], c[i], a[i]-(x-len1), len1); // 减去1的高度,结束天数改为len1
			
		} else day[i] = fcc(b[i], c[i], a[i], x); // 否则直接解
		
	}
	
//	for(int i=1;i<=n;++i) cout<<day[i]<<" ";
//	puts("");
	
	memset(vis, 0, sizeof(vis));
	vis[1] = 1;
	dfs2(1); // 树上不等式压缩
	
	for(int i=1;i<=n;++i) { // 存在无解答案
		if(day[i]<=0 || day[i]>x) return false;
	}
	
//	cout<<"Day: ";
//	for(int i=1;i<=n;++i) cout<<day[i]<<" ";
	
  // 构造序列
	sort(day+1, day+1+n); // O(nlogn)
	for(int i=1;i<=n;++i) {
		if(day[i] < i) {
        // 如果轮到第i天种的树,比最迟天数大,同样无解
			return false;
		}
	}
	
//	cout<<"Win!\n";
	
	return true;
}

int main() {
	
	scanf("%d", &n);
	for(int i=1;i<=n;++i) scanf("%lld%lld%lld", &a[i], &b[i], &c[i]);
	for(int i=1;i<=n-1;++i) {
		scanf("%d%d", &x, &y);
		Add_edge(x, y);
		Add_edge(y, x);
	}
	
	dep[1] = vis[1] = 1;
	dfs(1);
	
//	cout<<"Dep: ";
//	for(int i=1;i<=n;++i) cout<<dep[i]<<" ";
	
	int ll=0, rr=1e9+7, mid;
	while(ll < rr) {
		mid = (ll + rr) >> 1;
		if(check(mid)) rr = mid;
		else ll = mid+1;
//		puts("");
	}
	
	printf("%d\n", ll);
	
	return 0;
}
/*

4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
2 3
3 4
: 9
经原样例
修改为链式

压缩前day:
4 2 6 9
压缩后day:
1 2 6 9
ans = 9

*/
2023/10/24 15:38
加载中...