HYSBZ - 2243 染色

给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“ 112221 ” 由3段组成:“ 11 ” 、“ 222 ” 和“ 1 ” 。
请你写一个程序依次完成这m个操作。
Input
第一行包含2个整数n和m,分别表示节点数和操作数;
第二行包含n个正整数表示n个节点的初始颜色
下面 行每行包含两个整数x和y,表示x和y之间有一条无向边。
下面 行每行描述一个操作:
“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;
“Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。
Output
对于每个询问操作,输出一行答案。
Sample Input
6 5


2 2 1 2 1 1


1 2


1 3


2 4


2 5


2 6


Q 3 5


C 2 1 1


Q 3 5


C 5 1 2


Q 3 5


Sample Output
3


1


2
Hint

数N<=10^5,操作数M<=10^5,所有的颜色C为整数且在[0, 10^9]之间。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lson root<<1
#define rson  (root<<1)|1
using namespace std;
const int maxn=1e5+5;
struct node{
	int to,nxt;
}ed[maxn<<1];
int head[maxn],cnt;
int dep[maxn],sz[maxn],son[maxn],p[maxn],top[maxn],fa[maxn];
int initcol[maxn],ndcol[maxn],pos;
void addedge(int u,int v){
	ed[cnt].to=v;
	ed[cnt].nxt=head[u];
	head[u]=cnt++;
}
void dfs1(int u,int pre,int d){
	dep[u]=d;
	sz[u]=1;
	fa[u]=pre;
	for(int i=head[u];~i;i=ed[i].nxt){
		int v=ed[i].to;
		if(v!=pre){
			dfs1(v,u,d+1);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
	}
}
void dfs2(int u,int tp){
	top[u]=tp;
	p[u]=pos++;
	initcol[p[u]]=ndcol[u];
	if(son[u]==-1){
		return ;
	}
	dfs2(son[u],tp);
	for(int i=head[u];~i;i=ed[i].nxt){
		int v=ed[i].to;
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
struct T{
	int l,r,lc,rc,mark,num;
}tree[maxn<<2];
void pushup(int root){
	tree[root].lc=tree[lson].lc;
	tree[root].rc=tree[rson].rc;
	tree[root].num=tree[lson].num+tree[rson].num-(tree[lson].rc==tree[rson].lc);
	return ;
}
void pushdown(int root){
	if(tree[root].l==tree[root].r)return ;
	if(tree[root].mark==0)return ;
	tree[lson].mark=tree[rson].mark=tree[root].mark;
	tree[lson].lc=tree[lson].rc=tree[rson].lc=tree[rson].rc=tree[root].mark;
	tree[lson].num=tree[rson].num=1;
	tree[root].mark=0;
	return ;
}
void build(int l,int r,int root){		
	tree[root].l=l,tree[root].r=r;
	tree[root].lc=initcol[l];
	tree[root].rc=initcol[r];
	tree[root].mark=0;
	if(l==r){
		tree[root].num=1;
		return ;
	}
	int mid=l+r>>1;
	build(l,mid,lson);
	build(mid+1,r,rson);
	pushup(root);
	return ;
}
void update(int l,int r,int col,int root){
	if(tree[root].l==l&&tree[root].r==r){
		tree[root].lc=tree[root].rc=col;
		tree[root].num=1;
		tree[root].mark=col;
		return ;
	}
	pushdown(root);
	int mid=tree[root].l+tree[root].r>>1;
	if(r<=mid)update(l,r,col,lson);
	else if(l>mid)update(l,r,col,rson);
	else {
		update(l,mid,col,lson);
		update(mid+1,r,col,rson);
	}
	pushup(root);
	return ;
}
int ask(int a,int root){
	if(a==tree[root].r)return tree[root].rc;
	if(a==tree[root].l)return tree[root].lc;
	pushdown(root);
	int mid=tree[root].l+tree[root].r>>1;
	if(a<=mid)return ask(a,lson);
	else if(a>mid)return ask(a,rson);
}
int query(int l,int r,int root){
	if(tree[root].l==l&&tree[root].r==r){
		return tree[root].num;
	}
	pushdown(root);
	int mid=(tree[root].l+tree[root].r)>>1;
	if(r<=mid)return query(l,r,lson);
	else if(l>mid)return query(l,r,rson);
	else {
		return query(l,mid,lson)+query(mid+1,r,rson)-(tree[lson].rc==tree[rson].lc);
	}	
}
void up(int u,int v,int col){
	int f1=top[u],f2=top[v];
	while(f1!=f2){
		if(dep[f1]<dep[f2]){
			swap(f1,f2);
			swap(u,v);
		}
		update(p[f1],p[u],col,1);
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]<dep[v])swap(u,v);
	update(p[v],p[u],col,1);
}
int getnum(int u,int v){
	int f1=top[u],f2=top[v];
	int re=0;
	while(f1!=f2){
		if(dep[f1]<dep[f2]){
			swap(f1,f2);
			swap(u,v);
		}
		re+=query(p[f1],p[u],1)-(ask(p[fa[f1]],1)==ask(p[f1],1));
		u=fa[f1];
		f1=top[u];
	}
	if(dep[u]<dep[v])swap(u,v);
	re+=query(p[v],p[u],1);
	return re;
}
int main()
{
	memset(son,-1,sizeof(son));
	memset(head,-1,sizeof(head));
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&ndcol[i]);
	}
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v);
		addedge(v,u);
	}
	dfs1(1,0,0);
	dfs2(1,1);
	build(0,n,1);
	while(m--){
		char str[2];
		int a,b,c;
		scanf("%s%d%d",str,&a,&b);
		if(str[0]=='C'){
			scanf("%d",&c);	
			up(a,b,c);	
		}
		else {
			printf("%d\n",getnum(a,b));
		}
		getchar();
	}
	return 0;
}


<think>我们参考用户的问题,他询问的是2024年CSP-S(提高级)认证中的染色问题(P11233)的解法。根据引用[1]中的描述,题目要求是最大化相邻同色元素的最小值之和。通过分析,我们得到以下关键点: 1. 题目规则:当两个相同颜色的元素相邻,且它们之间没有其他同色元素时,得分等于这两个元素的值的最小值。 2. 观察:为了最大化得分,我们应该将相同数值的元素染成同一种颜色(红色或蓝色),并且将它们连续放置(即相邻),这样每个数值(除了第一个)都能与它前面最近的一个相同数值形成有效对。 3. 独立贡献:不同数值之间的贡献是独立的。对于每个数值v,如果它出现了cnt_v次,那么它最多可以形成(cnt_v-1)对相邻的同色元素。每一对的贡献是v(因为两个数都是v,最小值就是v),所以该数值的总贡献为v*(cnt_v-1)。 因此,整个问题的解法如下: 1. 统计每个数值出现的次数(注意数值范围,题目中最大值为10^6)。 2. 遍历所有数值,对于出现次数大于等于2的数值,计算其贡献:v*(cnt_v-1),并累加。 3. 输出总和。 注意:题目中可能有多个测试用例,所以需要处理T组数据。 代码实现时,我们使用一个长度为MAX_VAL+1的数组cnt(初始化为0)来统计每个数值的出现次数。然后读入数组,对于每个数a,如果a在范围内(<=MAX_VAL),则cnt[a]++。最后遍历1到MAX_VAL,累加贡献。 下面给出代码(已根据题目要求使用C++):</think>### 2024 CSP-S 染色问题解法详解(P11233) #### 核心思路 题目要求最大化相邻同色元素的最小值之和。通过分析规则,发现得分仅发生在相同数值的相邻位置(中间无同色元素)上,且**每个数值的贡献独立**。关键结论如下: 1. **同色聚合最优**:对于数值 $v$,将所有 $v$ 染成同色时贡献最大 - 例:`[5,2,5]` 染同色得 $5$(`5-5`),分色则得 $0$ 2. **贡献公式**:数值 $v$ 出现 $cnt_v$ 次时,最大贡献为: $$ \text{贡献}(v) = \begin{cases} v \times (cnt_v - 1) & \text{if } cnt_v \geq 2 \\ 0 & \text{otherwise} \end{cases} $$ - 每对相邻同色元素贡献 $v$(因 $\min(v,v)=v$) - $cnt_v$ 个元素可形成 $(cnt_v-1)$ 对有效相邻 #### 算法步骤 1. **频率统计** 遍历数组,统计每个数值出现次数 $cnt_v$ ```cpp vector<int> cnt(MAX_VAL + 1, 0); for (int i = 0; i < n; i++) { cin >> a; if (a <= MAX_VAL) cnt[a]++; } ``` 2. **贡献计算** 对每个 $v \in [1, \text{MAX\_VAL}]$ 计算贡献: ```cpp long long ans = 0; for (int v = 1; v <= MAX_VAL; v++) { if (cnt[v] >= 2) { ans += static_cast<long long>(v) * (cnt[v] - 1); } } ``` 3. **输出结果** 直接输出累加值 `ans` #### 复杂度分析 - **时间**:$O(n + \text{MAX\_VAL})$ - 统计频率 $O(n)$,计算贡献 $O(\text{MAX\_VAL})$ - **空间**:$O(\text{MAX\_VAL})$ 频率数组空间 #### 正确性验证 **样例输入**:`[3, 5, 2, 5, 1, 2, 1, 4]` - $1$:出现 $2$ 次 → $1 \times (2-1) = 1$ - $2$:出现 $2$ 次 → $2 \times (2-1) = 2$ - $5$:出现 $2$ 次 → $5 \times (2-1) = 5$ - 其他值:$cnt < 2$ → $0$ **总得分**:$1 + 2 + 5 = 8$(最优解:所有 $1,2,5$ 染同色)[^1] #### 优化扩展 1. **大数值范围($v \leq 10^9$)** 改用 `map` 统计频率: ```cpp unordered_map<int, int> cnt; for (int i = 0; i < n; i++) { cin >> a; cnt[a]++; } // 遍历map计算贡献 ``` - 时间:$O(n \log n)$,空间:$O(n)$ 2. **动态规划解法(备用思路)** 当规则复杂化时(如连续段极差),可用 DP: ```cpp // dp[i][c]: 前i个元素,末尾颜色为c时的最大得分 for (int i = 1; i <= n; i++) { for (int c : {RED, BLUE}) { dp[i][c] = max( dp[i-1][c] + score(a[i], c), // 延续同色 dp[i-1][!c] // 切换颜色 ); } } ``` 但本题证明独立贡献更优[^2],无需 DP #### 代码实现 ```cpp #include <iostream> #include <vector> using namespace std; const int MAX_VAL = 1000000; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) { int n; cin >> n; vector<int> cnt(MAX_VAL + 1, 0); for (int i = 0; i < n; i++) { int a; cin >> a; if (a <= MAX_VAL) cnt[a]++; } long long ans = 0; for (int v = 1; v <= MAX_VAL; v++) { if (cnt[v] >= 2) { ans += static_cast<long long>(v) * (cnt[v] - 1); } } cout << ans << '\n'; } return 0; } ``` ### 相关问题 1. **若染色规则改为取相邻同色元素的最大值,解法如何调整?** **答**:贡献公式不变,仍为 $v \times (cnt_v-1)$(因 $\max(v,v)=v$)[^1] 2. **多颜色(如三种)时能否增加得分?** **答**:不能。分组会减少有效数对数量,同色聚合仍最优(例:三个 $5$ 分两色最多得 $5$,同色得 $5 \times 2=10$) 3. **贪心策略为何无效?请举例说明** **答**:顺序染色无法保证全局最优。例 `[2,1,2]`: - 贪心:染 `[红,蓝,红]` 得 $\min(2,2)=2$ - 最优:全染红得 $2 \times (2-1)=2$(但若为 `[1,2,1]`,贪心可能得 $0$,最优得 $1$) 4. **若得分规则改为连续同色段的极差之和,如何设计算法?** **答**:需动态规划维护段信息: ```python # dp[i][c] = (当前段最小值, 当前段最大值, 总得分) for i in range(n): for c in colors: # 延续当前颜色段 new_min = min(dp[i-1][c].min, a[i]) new_max = max(dp[i-1][c].max, a[i]) score_add = new_max - new_min # 或新开颜色段 ... ``` 5. **如何应对元素删除/修改的变种问题?** **答**:需线段树维护区间频率,结合莫队算法处理动态询问($O(n\sqrt{n})$)[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值