P8089 『JROI-5』Color
Cocoly1990 · · 题解
D
part1 问题等价于给定一颗完全二叉树,求其包含根的子树个数。 首先,先考虑满二叉树。 我们可以发现,答案只和树的深度(dep)有关,可以想到用
显然
以
part2 类似的,我们也可以通过分别递归计算左右子树答案,再相乘的做法来对一颗不规则的树来计数。
以一颗深度为 5 ,底层节点数为 5 的树为例。我们可以拆成以 2 和 3 为根节点的子树分别递归地去求,求完后相乘。 我们按从上到下,从左到右给树标号,若
part3 我们可以发现,在我们的 dfs 中,有许多满二叉树,所以我们根本不需要对每个节点 dfs 一次。
比如以 3,4,11 为根节点的子树分别就是深度为 3, 2, 1 的满二叉树。 更形式化的,我们可以发现,对于每一个节点,其左右子树中必有一颗为满二叉树。 由完全二叉树的性质,若右子树不为满二叉树,则其左子树必为满二叉树,而若左子树不为满二叉树,则其底层节点根本没有覆盖到右子树,右子树为满二叉树。 所以,我们可以用 part1 的做法,预处理出所有满二叉树的答案。每次 dfs 的,直接调用即可。而我们只需要 dfs 一条链。(图中 1-2-5-10-20) 所以,我们的问题变为对于每个节点,确定其哪颗子树为满二叉树,并且求出子树高度。即我们需要求出从根节点到最后一个叶节点的路径,每次 dfs 路径上的下一个节点,直接调用求出另外一个节点的答案。 观察到本题给出底层节点个数的方式非常特殊,用二进制给出。暂时忽略满二叉树的情况,去掉第一个0,比如 5 为 0101。我们从根节点开始,对于这个二进制字串,从左到右,如果遇到 0 就走左子树,遇到 1 就走右子树,我们可以发现对于任意一棵树,我们都可以走到当前最后一个节点的下一个节点。
仔细研究一下,我们可以发现 5 的 00101,第一位的 0 表示其节点数小于 16 ,即位于根为 1 的子树上,而第二个 1 表示其节点数小于 8,位于 1 的左子树上,第三个 1 表示其节点数大于等于 4 ,位于 2 的右子树上。 由此,我们再考虑满二叉树,图中满二叉树底层节点为 16,表示为 10000,第一位的 1 表示其节点数大于等于 16,即其最后一个节点的下一个节点根本就不在根为 1 的子树上。 设其底层节点个数为
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5;
const int MOD = 998244353;
int dep, a[MAXN], dp[MAXN];
string s;
int dfs(int k) {
if (k == dep)
return 1; //到最后一层
if (a[k] == 0)
return (dfs(k + 1) + 1) * (dp[dep - k - 1] + 1) % MOD; //右子树为满二叉树,dfs左子树
else return (dp[dep - k] + 1) * (dfs(k + 1) + 1) % MOD; //左子树为满二叉树,dfs右子树
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
dp[1] = 1;
for (int i = 1; i < MAXN; ++i)
dp[i] = (dp[i - 1] + 1) * (dp[i - 1] + 1) % MOD; //预处理
int T;
cin >> T;
while (T--) {
cin >> dep >> s;
int pos = 0;
for (int i = 1; i < s.length(); ++i) {
a[i] = s[i] - '0';
if (a[i] == 1)
pos = i;
}
a[pos] = 0;
for (int i = pos + 1; i < dep; ++i)
a[i] = 1;//s-1 的二进制表达
cout << dfs(1) << endl;
}
return 0;
}
当然还有另一种优化方法,由 Part 2 我们得到转移方程
显然状态不是很好优化,那么思考如何优化转移。可以发现,每个
绿色表示处理过的,然后会发现每层只有两个节点需要向上传递。所以复杂度是对数级别的。
我们可以通过
具体的说,对
#include<bits/stdc++.h>
#define int long long
#define elif else if
#define ALL(x) x.begin(),x.end()
#define lowbit(x) (x&(-x))
using namespace std;
void fileio(const string &s)
{
freopen((s+".in").c_str(),"r",stdin);
freopen((s+".out").c_str(),"w",stdout);
}
const int INF=4e18;
inline int read()
{
int x=0;
bool flag=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
flag=0;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return (flag?x:~(x-1));
}
const int mod=998244353;
int t,dep,a[1000001];
vector<int> f[1000001];
char s1[1000000],s2[1000000];
void solve()
{
dep=read();//8 00111001 00100011
scanf("%s",s1);
scanf("%s",s2);//假设s1是最底层叶子节点,s2是次底层叶子节点。具体的需要二进制高精度。
reverse(s1,s1+dep);
reverse(s2,s2+dep);
for(int i=0;i<=dep;i++)
f[i].clear();
for(int i=0;i<dep;i++)
if(s1[i]=='1')
f[dep-i].push_back(a[i+1]);
for(int i=0;i<dep;i++)
if(s2[i]=='1')
f[dep-i-1].push_back(a[i+1]);
for(int i=dep;i;i--)
if(f[i].size()==1)
f[i-1].push_back(f[i][0]+1);
elif(f[i].size()==2)
f[i-1].push_back((f[i][0]+1)*(f[i][1]+1)%mod);
cout<<f[0][0]-1<<'\n';
}
signed main()
{
a[1]=1;
for(int i=2;i<=1000000;i++)
a[i]=(a[i-1]+1)*(a[i-1]+1)%mod;
t=read();
while(t--)
solve();
return 0;
}
隐藏图片:
【数据删除】