[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\)?那就算吧,比较也是减小复杂度的一个好办法。