[ZROJ]计算器

壹、题目描述

校内 OJ 题号 28818,或 LUOGU 团队题单,此处不给出题面。

贰、题解

根据这个 \(a_i,b_i\) 都是随机的,现在已有但不限于以下针对 \(a_i,b_i\) 的处理方案:

  • 对于两个 \(\lang a_1,b_1\rang\lang a_2,b_2\rang\),如果 \(a_1<b_1\)\(a_2<b_2\),那么我们称 \(\lang a_2,b_2\rang\) 全优于 \(\lang a_1,b_1\rang\),这样,我们只需要找到最全优的点即可,某队长给出证明,说对于每个点期望只会留下两个,所以这样复杂度就是对的;
  • 发现在深度比较深的情况下,两个大数的最高位不同的概率非常大,所以我们只需要保存最高的几位,再存下一个位数,在算到后面的时候忽略加法,只算乘法。这样可以过大样例,但是不知道最后结果是什么;

题解给出的解决方案和第二条类似,但是它是使用 \(\tt double\) 加上一个位数,即将每个数都保存为 \(a\times 10^k\) 的科学计数法的形式,找到最优叶子结点之后,使用 分治 + \(\tt NTT\) 算出最终答案,整体复杂度是 \(\mathcal O(n\log ^2n)\) 的,考虑使用压位减小常数。

叁、代码

在随机数据下就是 \(\tt std\),只是不知道为什么过不了更强的数据。

#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int maxn=5e5;
const int mod=998244353;
const int g[2]={3, 332748118};
const double eps=1e-8;
// if x>y, return 1
// if x==y, return 0;
// if x<y, return -1
inline int compare(double x, double y){
	return (x-y>-eps)-(x-y<eps);
}
inline int qkpow(int a, int n){
	int ret=1;
	for(; n>0; n>>=1, a=1ll*a*a%mod)
		if(n&1) ret=1ll*ret*a%mod;
	return ret;
}

int n;
int a[maxn+5], b[maxn+5];

// scientific notation
// the value equals to x*10^k
struct sci_type{
	double x; int k;
	sci_type(){x=0, k=0;}
	sci_type(double X, int K): x(X), k(K){}
	inline void operator =(double rhs){
		k=0;
		while(compare(rhs, 10.0)>0) ++k, rhs/=10;
		x=rhs;
	}
	inline void operator *=(double rhs){
		x=x*rhs;
		while(compare(x, 10.0)>0) ++k, x/=10;
	}
	inline void operator +=(double rhs){
		for(int i=1; i<=k; ++i) rhs/=10;
		x+=rhs;
	}
	inline int operator <(const sci_type rhs) const{
		if(k!=rhs.k) return k<rhs.k;
		return compare(x, rhs.x)<0;
	}
	inline int operator >(const sci_type rhs) const{
		return rhs<(*this);
	}
	inline void print(){
		printf("(%.10f, %d)", x, k);
	}
};

namespace tree{
	struct edge{
		int to, nxt;
		edge(){}
		edge(const int T, const int N): to(T), nxt(N){}
	}e[maxn*2+5];
	int tail[maxn+5], ecnt;
	inline void initial(){
		memset(tail, -1, sizeof tail);
	}
	inline void add_edge(const int u, const int v){
		e[ecnt]=edge(v, tail[u]); tail[u]=ecnt++;
		e[ecnt]=edge(u, tail[v]); tail[v]=ecnt++;
	}
	sci_type maxx; int ret;
	int fath[maxn+5];
	void dfs(const int u, const int par, sci_type val){
		fath[u]=par;
		val*=a[u], val+=b[u];
		int leaf=1;
		for(int i=tail[u], v; ~i; i=e[i].nxt)
			if((v=e[i].to)!=par)
				dfs(v, u, val), leaf=0;
		if(leaf){
			if(maxx<val) ret=u, maxx=val;
		}
	}
	inline int launch(){
		maxx=0;
		dfs(1, 0, sci_type(1, 0));
		return ret;
	}
}

namespace poly{
	int rev[maxn+5];
	int G[2][55], n, invn;
	inline void initial(){
		for(int j=1; j<=50; ++j){
			G[0][j]=qkpow(g[0], (mod-1)/(1<<j));
			G[1][j]=qkpow(g[1], (mod-1)/(1<<j));
		}
	}
	inline void prepare(const int len){
		for(n=1; n<len; n<<=1);
		invn=qkpow(n, mod-2);
		for(int i=0; i<n; ++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)?(n>>1):0);
	}
	inline void ntt(vector<int>&f, const int opt){
		f.resize(n);
		for(int i=0; i<n; ++i) if(i<rev[i])
			swap(f[i], f[rev[i]]);
		for(int p=2, cnt=1; p<=n; p<<=1, ++cnt){
			int len=p>>1, w=G[opt][cnt];
			for(int k=0; k<n; k+=p){
				int buf=1, tmp;
				for(int i=k; i<k+len; ++i, buf=1ll*buf*w%mod){
					tmp=1ll*buf*f[i+len]%mod;
					f[i+len]=(f[i]+mod-tmp)%mod;
					f[i]=(f[i]+tmp)%mod;
				}
			}
		}
		if(opt==1){
			for(int i=0; i<n; ++i)
				f[i]=1ll*f[i]*invn%mod;
		}
	}
}

struct bignum{
	vector<int>c;
	bignum(){c.clear();c.push_back(0);}
	inline void operator =(int rhs){
		c.clear();
		while(rhs){
			c.push_back(rhs%10);
			rhs/=10;
		}
		if(c.empty()) c.push_back(0);
	}
	inline void operator *=(bignum rhs){
		int len=c.size()+rhs.c.size();
		poly::prepare(len);
		poly::ntt(c, 0), poly::ntt(rhs.c, 0);
		for(int i=0; i<poly::n; ++i)
			c[i]=1ll*c[i]*rhs.c[i]%mod;
		poly::ntt(c, 1); c.resize(len);
		int res=0;
		for(int i=0; i<len; ++i){
			c[i]+=res;
			res=c[i]/10;
			c[i]%=10;
		}
		while(res) c.push_back(res%10), res/=10;
		while(c.back()==0) c.pop_back();
		if(c.empty()) c.push_back(0);
	}
	inline void operator +=(bignum rhs){
		int len=max(c.size(), rhs.c.size());
		c.resize(len), rhs.c.resize(len);
		int res=0;
		for(int i=0; i<len; ++i){
			c[i]=c[i]+rhs.c[i]+res;
			res=c[i]/10, c[i]%=10;
		}
		if(res) c.push_back(res);
	}
	inline void print(){
		for(int i=(int)c.size()-1; i>=0; --i)
			printf("%d", c[i]);
		putchar('\n');
	}
};

namespace chain{
	int p[maxn+5], n;
	inline void build(int u){
		n=0;
		while(u){
			p[++n]=u;
			u=tree::fath[u];
		}
		reverse(p+1, p+n+1);
	}
	struct mpair{bignum a, b;};
	mpair solve(const int l, const int r){
		if(l==r){
			mpair ret;
			ret.a=a[p[l]], ret.b=b[p[l]];
			return ret;
		}
		int mid=(l+r)>>1;
		mpair ls=solve(l, mid);
		mpair rs=solve(mid+1, r);
		ls.a*=rs.a;
		ls.b*=rs.a;
		ls.b+=rs.b;
		return ls;
	}
	inline void launch(){
		poly::initial();
		mpair ans=solve(1, n);
		ans.a+=ans.b;
		ans.a.print();
	}
}

inline void input(){
	n=readin(1);
	tree::initial();
	for(int i=1; i<=n; ++i) a[i]=readin(1), b[i]=readin(1);
	int u, v;
	for(int i=1; i<n; ++i){
		u=readin(1), v=readin(1);
		tree::add_edge(u, v);
	}
}

signed main(){
//	freopen("calc.in", "r", stdin);
//	freopen("calc.out", "w", stdout);
	input();
	int leaf=tree::launch();
	chain::build(leaf);
	chain::launch();
	return 0;
}

肆、用到の小 \(\tt trick\)

注意随机数据的特性,考虑在极限的时候随机下很难出现重复的、或者在某些情况下深度较浅,比如随机一棵树,深度在 \(\log\) 级别之类。

难道也得把分治算上用到的 \(\tt trick\)?那就算吧,比较也是减小复杂度的一个好办法。

posted @ 2021-03-01 16:37  Arextre  阅读(119)  评论(0编辑  收藏  举报