[BZOJ 3757]苹果树

3757: 苹果树


Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 1166  Solved: 453
[Submit][Status][Discuss]
Description
    神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个到n之间的正整数来表示一种颜色。树上一共有n个苹果。每个苹果都被编了号码,号码为一个1到n之间的正整数。我们用0代表树根。只会有一个苹果直接根。
有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为u的苹果出发,由树枝走到编号为v的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果u和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色a就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色a的苹果,又出现了颜色b的苹果,这个人只会算入颜色b,而不会把颜色a算进来。
神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?


Input


输入第一行为两个整数n和m,分别代表树上苹果的个数和前来膜拜的人数。
接下来的一行包含n个数,第i个数代表编号为i的苹果的颜色Coli。
接下来有n行,每行包含两个数x和y,代表有一根树枝连接了苹果x和y(或者根和一个苹果)。
接下来有m行,每行包含四个整数u、v、a和b,代表这个人要数苹果u到苹果v的颜色种数,同时这个人认为颜色a就是颜色b。如果a=b=0,则代表这个人没有患色盲症。
Output


输出一共m行,每行仅包含一个整数,代表这个人应该数出的颜色种数。


0<=x,y,a,b<=N
N<=50000
1<=U,V,Coli<=N
M<=100000


这个人色盲会把红的看成红的QAQ!

树上莫队算法

就是把树分个块,然后跑莫队

块的大小就是sqrt(n)啦

你只需要一遍DFS标个号就好啦(然后你需要一个栈来帮助你标号,储存DFS序呢)

stack<int>S;
void dfs(int u){
	S.push(u);
	for(int i = h[u]; i; i = edge[i].next){
		int v = edge[i].to;
		if(v == fa[u])continue;
		fa[v] = u;
		dep[v] = dep[u] + 1;
		dfs(v);
		size[u] += size[v];
		if(size[u] >= B){
			tim ++;
			for(;;){
				int t = S.top();
				if(t == u)break;
				pos[t] = tim;
				S.pop();
			}
			size[u] = 0;
		}
	}size[u] ++;
}




不过指针的移动好烦啊。

其实也还好啦。

ans = S(root, u) ^ S(root, v) ^ LCA(u, v)

设T(u, v) = S(root, u) ^ S(root, v)

考虑一个的移动

T(u, v) => T(u',v)

T(u, v) = S(root, u) ^ S(root, v)

T(u', v) = S(root, u') ^ S(root, v)

我们需要做的就是

S(root, u) ^ S(root, v)  ^ S(root, u') ^ S(root, v)

S(root, u)  ^ S(root, u') 

也就是说LCA(u, u')不能取反,其余的全部取反。

然而统计答案需要S(u, v)

那么加上LCA(u, v)

然后减掉再移动就好了





#include 
   
   
    
    
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
         #include 
        
          #define maxn 200010 using namespace std; void read(int &num){ num = 0;char ch = getchar(); for(; ch < '!'; ch = getchar()); for(; ch > '!'; ch = getchar()) num = num * 10 + ch - 48; } int c[maxn]; int n, m; struct Edge{int to, next;}edge[maxn]; int h[maxn], cnt; void add(int u, int v){ cnt ++; edge[cnt].to = v; edge[cnt].next = h[u]; h[u] = cnt; } int fa[maxn], dep[maxn], dfn[maxn], que[maxn], dfs_clock; int cur[maxn], size[maxn]; int pos[maxn], B, tim; stack 
         
           S; void dfs(int u){ S.push(u); for(int i = h[u]; i; i = edge[i].next){ int v = edge[i].to; if(v == fa[u])continue; fa[v] = u; dep[v] = dep[u] + 1; dfs(v); size[u] += size[v]; if(size[u] >= B){ tim ++; for(;;){ int t = S.top(); if(t == u)break; pos[t] = tim; S.pop(); } size[u] = 0; } }size[u] ++; } int anc[maxn][20]; void LCA(){ memset(anc, -1, sizeof anc); for(int i = 1; i <= n; i ++) anc[i][0] = fa[i]; for(int j = 1; 1 << j <= n; j ++){ for(int i = 1; i <= n; i ++){ int a = anc[i][j - 1]; if(a == -1)continue; anc[i][j] = anc[a][j - 1]; } } } int askLCA(int p, int q){ if(dep[p] < dep[q])swap(p, q); int log; for(log = 1; 1 << log <= dep[p]; log ++);log --; for(int i = log; i >= 0; i --) if(~anc[p][i] && dep[anc[p][i]] >= dep[q]) p = anc[p][i]; if(p == q)return p; for(int i = log; i >= 0; i --) if(~anc[p][i] && anc[p][i] != anc[q][i]) p = anc[p][i], q = anc[q][i]; return fa[p]; } struct Qry{ int u, v, a, b, id; bool operator<(const Qry& k)const{ if(pos[u] != pos[k.u])return pos[u] < pos[k.u]; return pos[v] < pos[k.v]; } void instate(){ read(u), read(v), read(a), read(b); //if(dfn[u] > dfn[v])swap(u, v); } }q[maxn]; int ans[maxn], ret; int vis[maxn], is[maxn]; void ADD(int u){ if(u == 0)return; if(is[u]){ vis[c[u]] --; if(vis[c[u]] == 0)ret --; } else{ if(vis[c[u]] == 0)ret ++; vis[c[u]] ++; } is[u] ^= 1; } void Move(int u, int v){ while(u != v){ if(dep[u] < dep[v]) swap(u, v); ADD(u); u = fa[u]; } } int ask_ans(int i){ if(vis[0]){ vis[0] = false; ret --; } if(q[i].a != q[i].b && vis[q[i].a] && vis[q[i].b]) return ret - 1; return ret; } void solve_Mo(){ sort(q + 1, q + 1 + m); int l = 0, r = 0, LCA; for(int i = 1; i <= m; i ++){ Move(l, q[i].u), l = q[i].u; Move(r, q[i].v), r = q[i].v; LCA = askLCA(l, r); ADD(LCA); ans[q[i].id] = ask_ans(i); ADD(LCA); } } int main(){ read(n), read(m); B = sqrt(n); for(int i = 1; i <= n; i ++) read(c[i]); int u, v; for(int i = 1; i <= n; i ++){ read(u), read(v); add(u, v); add(v, u); } dfs(0); while(!S.empty()){ pos[S.top()] = tim; S.pop(); } LCA(); for(int i = 1; i <= m; i ++){ q[i].instate(); q[i].id = i; } solve_Mo(); for(int i = 1; i <= m; i ++) printf("%d\n", ans[i]); return 0; } /*PS:炸了三个小时的树上莫队T^T*/ 
          
         
       
      
      
     
     
    
    
   
   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值