A
B

CF1905E One-X题解

CF1905E One-X

思路详情:

首先读题发现:可以正难则反,即去枚举集合的LCA,也就是从上向下遍历。

然后考虑对于以 \(id\) 为根的子树的贡献,设其为 \(dp_{id}\),设以 \(id\) 为根的子树为区间 \(l\)\(r\) ,长度为 \(len_{id}\) ,我们可以发现其左子树的 \(siz_{lid}\)\((len + 1) / 2\) ,右子树的 \(siz_{rid}\)\(len / 2\)

则节点 \(id\) 的贡献为 \((2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\) (题目中给的集合数公式 \(2 ^ n - 1\) )。

\(id\) 为根的子树的贡献为 \(dp_{lid} + dp_{rid} + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\)

就有了转移方程 \(dp_{id} = dp_{lid} + dp_{rid} + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\)

于是我们就能得到 19tps 的暴力代码:

#include<bits/stdc++.h>
#define int long long
#define putchar_unlocked _putchar_nolock
#define getchar_unlocked _getchar_nolock
#define ent putchar_unlocked('\n')
#define con putchar_unlocked(' ')
#define Blue_Archive return 0
using namespace std;
const int N = 65;
const int mod = 998244353;
const int inv = 499122177;

int T;
int n;
int ans;

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	}
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline int qpow(int a,int b)
{
	int res = 1;
	while(b)
	{
		if(b & 1) res = (res * a) % mod;
		a = (a * a) % mod;
		b >>= 1;
	}
	return res;
}

//容斥一下:res = (2 ^ (siz[ls] * siz[rs]) - 1) * val = (2 ^ siz[id] - 1 - (2 ^ siz[ls] - 1) - (2 ^ siz[rs] - 1)) * val = (qpow(2,len) - qpow(2,len / 2) - qpow(2,(len + 1) / 2) + 1) * val

inline int build(int l,int r,int val,int id)
{
	if(l == r) return val;
	int len = r - l + 1,mid = (l + r) >> 1;
	int res = ((qpow(2,len / 2) - 1) * (qpow(2,(len + 1) / 2) - 1) % mod * val) % mod;
	if(len % 2 == 1) return ((res + build(l,mid,2 * val,id)) % mod + build(mid + 1,r,2 * val + id,id)) % mod;
	else return (res + build(l,mid,4 * val + id,id * 2)) % mod;
}

signed main()
{
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	T = read();
	while(T --)
	{
		n = read();
		ans = build(1,n,1,1);
		write((ans % mod + mod) % mod);ent;
	}
	Blue_Archive; 
}

之后神秘的来了,我们发现这个柿子里的 \(id\) 很恶心,貌似可以维护一下。

考虑更优的时间复杂度,发现如果将搜索改成记忆化搜索的话将会获得严格 \(O(log_{n})\) 的时间复杂度,可以通过此题。

于是就考虑搞一个跟 \(id\) 有关的柿子。

发现貌似是一个一次函数。

\(dp_{id}\)\(k_{id} * id + b_{id}\) ,发现 \(k_{id}\)\(b_{id}\) 都能从 \(k_{lid}\)\(k_{rid}\)\(b_{lid}\)\(b_{rid}\) 转移过来。

所以就可以转移了。

\(dp_{id} = dp_{lid} + dp_{rid} + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\)

\(k_{id} * id + b_{id} = k_{lid} * lid + b_{lid} + k_{rid} * rid + b_{rid} + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\)

\(k_{id} * id + b_{id} = k_{lid} * id * 2 + b_{lid} + k_{rid} * (id * 2 + 1) + b_{rid} + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id\)

\(k_{id} * id + b_{id} = k_{lid} * id * 2 + k_{rid} * id * 2 + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1) * id + k_{rid} + b_{rid} + b_{lid}\)

\(k_{id} * id + b_{id} = (k_{lid} * 2 + k_{rid} * 2 + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1)) * id + k_{rid} + b_{rid} + b_{lid}\)

所以可得:

\(k_{id} = k_{lid} * 2 + k_{rid} * 2 + (2 ^ {siz_{lid}} - 1) * (2 ^ {siz_{rid}} - 1)\)

\(b_{id} = k_{rid} + b_{rid} + b_{lid}\)

最后答案就为以 \(1\) 为根的子树的贡献,即为 \(dp_i = dp_k * 1 + dp_b = dp_k + dp_b\)

然后我们就可以愉快地 A 掉这道题了。

tips:因为 \(id\) 特别大,开数组开不下,有因为是记忆化搜索,不需要存很多东西,所以空间复杂度可以保证,至于怎么开嘛,用 \(map\) 就行了。

#include<bits/stdc++.h>
#define int long long 
#define lid (id << 1)
#define rid (id << 1 | 1)
#define putchar_unlocked _putchar_nolock
#define getchar_unlocked _getchar_nolock
#define ent putchar_unlocked('\n')
#define con putchar_unlocked(' ')
#define Blue_Archive return 0
using namespace std;
const int mod = 998244353;

int T;
int n;

struct miku
{
	int k;
	int b;
}ans;

map<int,miku> mp;

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	}
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline int qpow(int a,int b)
{
	int res = 1;
	while(b)
	{
		if(b & 1) res = (res * a) % mod;
		a = (a * a) % mod;
		b >>= 1;
	}
	return res;
}

inline miku build(int id,int len)
{
	if(mp.count(len)) return mp[len];
	if(len == 1) return mp[len] = (miku){1,0};
	if(len == 0) return mp[len] = (miku){0,0};
	int x = (len + 1) / 2,y = len / 2;
	miku tx = build(lid,x);
	miku ty = build(rid,y);
	miku res = (miku){(tx.k * 2 % mod + ty.k * 2 % mod) % mod + (qpow(2,x) - 1) * (qpow(2,y) - 1) % mod,(tx.b + ty.b + ty.k) % mod};
	return mp[len] = res;
}

signed main()
{
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	T = read();
	while(T --)
	{
		n = read();
		mp.clear();
		ans = build(1,n);
		write((ans.k + ans.b) % mod);ent;
	}
	Blue_Archive;
}
posted @ 2025-08-19 11:49  MyShiroko  阅读(12)  评论(0)    收藏  举报