P5852 [USACO19DEC]Bessie's Snow Cow P 题解

题意分析

给定一棵树,定义树上一个点的权值为它染的不同颜色的个数(一个点会有多种颜色)

你需要支持两种操作:

  • \(x\)的子树全部染上(添加)某种颜色
  • 查询给定\(x\)的子树内的所有点的权值之和

考场思路

对于每一个点直接使用\(set\)记录其染上的颜色,并在修改时记录每一个点的子树权值后缀和,这样可以做到\(O(n\log n)\)的单次修改以及\(O(1)\)的查询,但是考虑到存在\(Q(1\leq Q \leq 10^5)\)次操作,最坏复杂度就是\(O(Qn\log n)\)(不断进行修改操作),肯定会炸。

考场代码

#include<iostream> 
#include<cstring>
#include<vector>
#include<set>
using namespace std ;
namespace IO
{
	const size_t SIZE = 1 << 16 ;
	#define isdigit(ch) (ch >= '0' && ch <= '9')
	namespace Read
	{
		char buf[SIZE] , *_now = buf , *_end = buf ;
		char getchar()
		{
			if(_now == _end)
			{
				_now = _end = buf ;
				_end += fread(buf , 1 , SIZE , stdin) ;
				if(_now == _end)
					return EOF ;
			}
			return *(_now++) ;
		}
		template<typename T>
		void read(T& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
			w *= f ;
		}
		void read(char& ch)
		{
			char tmp = getchar() ;
			while(tmp == ' ' || tmp == '\n')
				tmp = getchar() ;
			ch = tmp ;
		}
		void read(char* s)
		{
			char ch = getchar() ;
			int len = 0 ;
			while((ch == ' ' || ch == '\n') && ch != EOF)
				ch = getchar() ;
			while(ch != ' ' && ch != '\n' && ch != EOF)
				s[len++] = ch , ch = getchar() ;
			s[len] = '\0' ;
		}
		class qistream
		{
			public:
			template<typename T>
			qistream& operator>>(T& w)
			{
				read(w) ;
				return *this ;
			}
			qistream& operator>>(char* s)
			{
				read(s) ;
				return *this ;
			}
		} qcin ;
	}
	namespace Write
	{
		char buf[SIZE] , *p = buf ;
		void flush()
		{
			fwrite(buf , 1 , p - buf , stdout) ;
			p = buf ;
		}
		void putchar(char ch)
		{
			if(p == buf + SIZE)
				flush() ;
			*p = ch ;
			++p ;
		}
		class Flush{public:~Flush(){flush() ;} ;}_;
		template<typename T>
		void write(T x)
		{
			char st[50] ;
			int len = 0 ;
			if(x < 0)
				putchar('-') , x = -x ;
			do
			{
				st[++len] = x % 10 + '0' ;
				x /= 10 ;
			} while(x) ;
			while(len)
				putchar(st[len--]) ;
		}
		void write(const char* s)
		{
			int len = strlen(s) ;
			for(int i = 0 ; i < len ; ++i)
				putchar(s[i]) ;
		}
		void write(char* s)
		{
			int len = strlen(s) ;
			for(int i = 0 ; i < len ; ++i)
				putchar(s[i]) ;
		}
		void write(char ch)
		{
			putchar(ch) ;
		}
		class qostream
		{
			public:
			template<typename T>
			qostream& operator<<(T x)
			{
				write(x) ;
				return *this ;
			}
			qostream& operator<<(const char* s)
			{
				write(s) ;
				return *this ;
			}
		} qcout ;
	}
	using Read::qcin ;
	using Write::qcout ;
}
using namespace IO ;
#define int long long
int n = 0 , q = 0 ;
set<int> color[100005] ;
int fa[100005] = {} , sum[100005] = {} ;
vector<int> edge[100005] ;
void build(int now , int father)
{
	fa[now] = father ;
	int siz = edge[now].size() ;
	for(int i = 0 ; i < siz ; ++i)
		if(edge[now][i] != father)
			build(edge[now][i] , now) ;
}
void pushdown(int now , int c)
{
	color[now].insert(c) ;
	int siz = edge[now].size() ;
	for(int i = 0 ; i < siz ; ++i)
		if(edge[now][i] != fa[now])
			pushdown(edge[now][i] , c) ;
}
void get_sum(int now)
{
	sum[now] = color[now].size() ;
	int siz = edge[now].size() ;
	for(int i = 0 ; i < siz ; ++i)
		if(edge[now][i] != fa[now])
		{
			get_sum(edge[now][i]) ;
			sum[now] += sum[edge[now][i]] ;
		}
}
signed main()
{
	freopen("snow.in" , "r" , stdin) ;
	freopen("snow.out" , "w" , stdout) ;
	qcin>>n>>q ;
	for(int i = 1 ; i < n ; ++i)
	{
		int a = 0 , b = 0 ;
		qcin>>a>>b ; 
		edge[a].push_back(b) , edge[b].push_back(a) ;
	}
	build(1 , 1) ;
	while(q--)
	{
		short op = 0 ;
		qcin>>op ;
		if(op == 1)
		{
			int x = 0 , c = 0 ;
			qcin>>x>>c ;
			pushdown(x , c) ;
			get_sum(1) ;
		}
		else
		{
			int x = 0 ;
			qcin>>x ;
			qcout<<sum[x]<<'\n' ;
		}
	}
	return 0 ;
}

正解分析

考虑我们上面的暴力算法其实不仅时间会炸,而且空间其实也是十分玄学的\(O(nc)\),不论怎样都可以被卡爆,所以我们考虑先从空间方面入手优化。

显然,\(set\)是必要的,因为我们不能让一个点被同一种颜色染两次,所以我们考虑反向存储来优化空间,即对于每一种颜色开一个\(set\),维护染成了这个颜色的点,这样空间复杂度便可以变成\(O(n)\)级别的。

然而,时间是还是无法过去的,所以考虑直接从答案贡献来源下手。

我们可以发现对于一个点(假定为\(u\))其实只存在两种情况:

  1. \(u\)祖先的子树均被染色,则\(u\)子树也会被染成该颜色,其贡献即为\(u\)子树的大小
  2. \(u\)子树已经被染成该颜色,则要加回来

显然,我们可以对这两种情况分别用一个树状数组进行维护,情况一为区间加,单点查;情况二为区间查,单点加。

正解代码

#include<iostream> 
#include<cstring>
#include<vector>
#include<set>
using namespace std ;
namespace IO
{
	const size_t SIZE = 1 << 16 ;
	#define isdigit(ch) (ch >= '0' && ch <= '9')
	namespace Read
	{
		char buf[SIZE] , *_now = buf , *_end = buf ;
		char getchar()
		{
			if(_now == _end)
			{
				_now = _end = buf ;
				_end += fread(buf , 1 , SIZE , stdin) ;
				if(_now == _end)
					return EOF ;
			}
			return *(_now++) ;
		}
		template<typename T>
		void read(T& w)
		{
			w = 0 ;
			short f = 1 ;
			char ch = getchar() ;
			while(!isdigit(ch))
			{
				if(ch == '-')
					f = -1 ;
				ch = getchar() ;
			}
			while(isdigit(ch))
				w = (w << 1) + (w << 3) + (ch ^ 48) , ch = getchar() ;
			w *= f ;
		}
		void read(char& ch)
		{
			char tmp = getchar() ;
			while(tmp == ' ' || tmp == '\n')
				tmp = getchar() ;
			ch = tmp ;
		}
		void read(char* s)
		{
			char ch = getchar() ;
			int len = 0 ;
			while((ch == ' ' || ch == '\n') && ch != EOF)
				ch = getchar() ;
			while(ch != ' ' && ch != '\n' && ch != EOF)
				s[len++] = ch , ch = getchar() ;
			s[len] = '\0' ;
		}
		class qistream
		{
			public:
			template<typename T>
			qistream& operator>>(T& w)
			{
				read(w) ;
				return *this ;
			}
			qistream& operator>>(char* s)
			{
				read(s) ;
				return *this ;
			}
		} qcin ;
	}
	namespace Write
	{
		char buf[SIZE] , *p = buf ;
		void flush()
		{
			fwrite(buf , 1 , p - buf , stdout) ;
			p = buf ;
		}
		void putchar(char ch)
		{
			if(p == buf + SIZE)
				flush() ;
			*p = ch ;
			++p ;
		}
		class Flush{public:~Flush(){flush() ;} ;}_;
		template<typename T>
		void write(T x)
		{
			char st[50] ;
			int len = 0 ;
			if(x < 0)
				putchar('-') , x = -x ;
			do
			{
				st[++len] = x % 10 + '0' ;
				x /= 10 ;
			} while(x) ;
			while(len)
				putchar(st[len--]) ;
		}
		void write(const char* s)
		{
			int len = strlen(s) ;
			for(int i = 0 ; i < len ; ++i)
				putchar(s[i]) ;
		}
		void write(char* s)
		{
			int len = strlen(s) ;
			for(int i = 0 ; i < len ; ++i)
				putchar(s[i]) ;
		}
		void write(char ch)
		{
			putchar(ch) ;
		}
		class qostream
		{
			public:
			template<typename T>
			qostream& operator<<(T x)
			{
				write(x) ;
				return *this ;
			}
			qostream& operator<<(const char* s)
			{
				write(s) ;
				return *this ;
			}
		} qcout ;
	}
	using Read::qcin ;
	using Write::qcout ;
}
using namespace IO ;
int n = 0 , m = 0 ;
struct BIT
{
	#define lowbit(x) (x & (-x))
	int tree[100005] = {} ;
	void add(int x , int y)
	{
		while(x <= n)
			tree[x] += y , x += lowbit(x) ;
	}
	int query(int x)
	{
		int ans = 0 ;
		while(x)
			ans += tree[x] , x -= lowbit(x) ;
		return ans ;
	}
} t1 , t2 ;
vector<int> edge[100005] ;
int dfn[100005] = {} , siz[100005] = {} , id[100005] = {} , tot = 0 ;
set<int> color[100005] ;
void dfs(int now , int father)
{
	dfn[now] = ++tot , id[tot] = now , siz[now] = 1 ;
	int size = edge[now].size() ;
	for(int i = 0 ; i < size ; ++i)
		if(edge[now][i] != father)
			dfs(edge[now][i] , now) , siz[now] += siz[edge[now][i]] ;
}
int main()
{
	freopen("snow.in" , "r" , stdin) ;
	freopen("snow.out" , "w" , stdout) ;
	qcin>>n>>m ;
	for(int i = 1 ; i < n ; ++i)
	{
		int a = 0 , b = 0 ;
		qcin>>a>>b ;
		edge[a].push_back(b) , edge[b].push_back(a) ;
	}
	dfs(1 , 1) ;
	while(m--)
	{
		int op = 0 ;
		qcin>>op ;
		if(op == 1)
		{
			int x = 0 , c = 0 ;
			qcin>>x>>c ;
			auto it = color[c].lower_bound(dfn[x]) ;
			if(it != color[c].begin() && dfn[id[*prev(it)]] + siz[id[*prev(it)]] >= dfn[x] + siz[x])
				continue ;
			while(it != color[c].end() && *it < dfn[x] + siz[x])
			{
				t1.add(*it , -1) ;
				t1.add(*it + siz[id[*it]] , 1) ;
				t2.add(*it , -siz[id[*it]]) ;
				color[c].erase(it++) ;
			}
			color[c].insert(dfn[x]) , t1.add(dfn[x] , 1) , t1.add(dfn[x] + siz[x] , -1) , t2.add(dfn[x] , siz[x]) ;
		}
		else
		{
			int x = 0 ;
			qcin>>x ;
			qcout<<(siz[x] * t1.query(dfn[x]) + t2.query(dfn[x] + siz[x] - 1) - t2.query(dfn[x]))<<'\n' ;
		}
	}
	return 0 ;
}

posted @ 2024-11-21 20:48  Torrentolf  阅读(13)  评论(0)    收藏  举报