#C220816B. 小凯的疑惑
#C220816B. 小凯的疑惑
C220816B (校内模拟赛)
背景
注意:本题采用捆绑测试。
题目描述
小凯正在玩一个寻宝游戏。总共有 \(n\) 个不同的藏宝地点,共 \(n-1\) 条道路把这些地点连接起来。小凯知道了第 \(i\) 个地点宝藏的价值为 \(v_i\) ,且如果在某个地点选了宝藏,那么所有与这个地点有直接道路相连的位置的宝藏会直接消失。最终小凯获得的总价值为他选的所有宝藏价值的乘积。
小凯想获得最大总价,但他非常疑惑。因此他请你帮忙求出价值最大的选择方案的价值 \(mod \ 998244353\)。
输入格式
第一行输入一个正整数 \(n\) ,接下来一行共 \(n\) 个正整数,表示每个点的权值 \(v_i\)。
接下来共 \(n-1\) 行每行两个数 \(x,y\),表示 \(x,y\) 之间有一条边。
输出格式
输出共一行, 输出最大价值 \(mod \ 998244353\)。
样例
输入数据 1
5
3 2 4 5 4
1 2
2 3
3 4
3 5
输出数据 1
60
样例1说明
选 1、4、5 号地点最优。
数据规模与约定
subtask1(20pts) : \(1≤n≤19,1≤v_i≤10\)。
subtask2(20pts) :保证最终答案在取模之前\(≤4×10^{18}\)
subtask3(20pts) :保证为藏宝地点的结构为一条链。
subtask4(40pts) :无特殊限制。
对于 100% 的数据,\(1≤n≤2×10^5,1≤v_i≤10^9\),且保证价值在某一范围内随机。
Solution
按照每个 subtask 进行讨论。
Subtask1
这部分应该是给爆搜的,就不再提了。
Subtask2
取模之前 \(\le 4\times 10^{18}\),也就意味着我们可以用普通的树形 DP 求出答案过后取模即可。这种树形 DP 很板,所以这部分也基本可以看作是暴力分了。
设 \(f[x][0]\) 表示不选 \(x\) 在 \(x\) 的子树中可以获得的最大价值,\(f[x][1]\) 表示选 \(x\) 在 \(x\) 的子树中获得的最大价值,那么可以很轻松的推出状态转移方程:
最后答案为 \(\max\{f[1][0],f[1][1]\} \bmod 998244353\)。
Subtask3
这一部分的数据是树退化成为了链,也就是将原问题变成了序列上的问题(我没想明白为什么设置这个 subtask,如果没有推出正解这部分的分应该也是不可做的才对啊)。
Subtask4
继续 Subtask2 的思路,如果在转移的时候进行取模,会出现转移 \(f[x][0]\) 的时候不知道到底应该从哪个转移(因为取模后无法判断大小),并且因为答案是乘积的形式,并且值域极大,如果写高精绝对超时空。考虑将答案表示成某个数的次幂的形式,那么乘积的转移就变成了加法的转移了,这样就很好的将值域给大幅降低了。将值表示成为 \(\ln\) 的形式进行转移,多维护一个数组用于记录 \(\ln\) 形式下的答案,多出的这个数组只是用来帮助我们知道转移的时候到底应该选择哪一个进行转移,并不是用来计算答案的(会出现精度问题)。
Code
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int __int128
using namespace std;
void read(auto &k)
{
k=0;auto flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
void write(auto x) {if (x<0) {putchar('-');write(-x);return;}if (x>9) write(x/10);putchar(x%10+'0');}
void writewith(auto x,char c) {write(x);putchar(c);}
const int _SIZE=2e5,mod=998244353;
int n;
int v[_SIZE+5],f[_SIZE+5][2];
long double ff[_SIZE+5][2],vv[_SIZE+5];
struct EDGE{
int nxt,to;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y)
{
++tot;
edge[tot].nxt=head[x];
edge[tot].to=y;
head[x]=tot;
}
void dfs(int x,int fa)
{
ff[x][0]=0,ff[x][1]=vv[x];//ff用于存储答案的ln形式
f[x][0]=1,f[x][1]=v[x];//f还是用来记录答案取模后的结果
for (int i=head[x];i;i=edge[i].nxt)
{
int twd=edge[i].to;
if (twd==fa) continue;
dfs(twd,x);
if (ff[twd][0]<ff[twd][1]) //ff只用来判断应该从哪里转移,不作为真实答案的记录,真实答案仍然记录在f中
ff[x][0]+=ff[twd][1],f[x][0]=(f[x][0]*f[twd][1])%mod;//ln形式下乘法变为加法,值域绝对不会爆
else
ff[x][0]+=ff[twd][0],f[x][0]=(f[x][0]*f[twd][0])%mod;
ff[x][1]+=ff[twd][0];
f[x][1]=(f[x][1]*f[twd][0])%mod;
}
}
signed main()
{
read(n);
for (int i=1;i<=n;i++) read(v[i]),vv[i]=logl(v[i]);//取ln
for (int i=1;i<n;i++)
{
int u,v;read(u),read(v);
AddEdge(u,v),AddEdge(v,u);
}
dfs(1,0);
if (ff[1][0]<ff[1][1]) writewith(f[1][1],'\n');
else writewith(f[1][0],'\n');
return 0;
}