瑶瑶的动感光波(加强版)
Time Limit:26000/13000MS (Java/Others)
Memory Limit:256000/128000 KB (Java/Others)
Problem Description
瑶瑶(tsyao)最近在练功—动感光波(适厉害呀(有错别字?)),瑶瑶有一个很大的练功房(其实是以前练舞蹈的么 ),练功房很大,可以分成n个区域。为了方便练功,练功房的布置很特别,所以我们可以把练功房看做以1号区域为根节点的,有n个节点的树。练功房每个区域有着不同的状态,有的区域雾气比较大,发挥动感光波的威力会消耗较大功力,有的区域很厉害,能够发挥动感光波的极大威力QAQ( 惊呆了)。每个区域都会有雾气ai,雾气会消耗ai的功力,能够发挥的威力bi这两个属性。瑶瑶的动感光波更厉害了,能够分成两个叉(每次使用动感光波时只能分叉一次),分别攻击不同的区域,当然,也可以不分叉。瑶瑶每次会选择一个区域z,然后再选择另外两个区域x和y,向x,y发出动感光波。这里,瑶瑶选择的z一定是x和y的祖先。
由于瑶瑶偷懒,不喜欢练功,因此她功力有限,每次发动动感光波途径一些区域时,会纠结是否在改区域消耗功力来获取威力(伤害?)。
给出m个查询,每次输入z,x,y,w四个整数,其中w为使用的功力。萌萌的瑶瑶想知道每次能够发挥最大为多少的威力。
Input
第一行 两个整数n,m 。
接下来n行,每行两个整数ai,bi,表示第i号区域的两个属性。
接下来n-1行,每行两个整数ci,di,表示区域ci与区域di相接。
接下来m行,每行四个整数z,x,y,w。
Output
Sample Input
9 1
50 100
1 1
30 1
30 1
1 1
50 100
1 1
50 100
30 1
1 2
2 3
3 4
3 5
3 6
4 7
4 8
5 9
2 7 9 31
Sample Output
3
Hint
图片为样例解释:
对于 100%的数据,有1 ≤ n ≤ 3000 , 1 ≤ m ≤ 10000 , 1 ≤ ai ≤ 50 , 1 ≤ bi ≤ 1000 , 1 ≤ x,y,z,ci,di ≤ n , 1 ≤ w ≤ 50 。
单组输入,共3组数据。
(由于出题人的疏忽,错误估算了暴力的时间复杂度,比赛时数据范围小了T_T,现在数据已更正:m≤1000 → m≤10000)
Source
Manager
本题是 瑶瑶的动感光波 这题的加强版,不得不说还是有点丧心病狂的。。
首先,题目给了n(n <= 3000)个点,m(m <= 10000)次询问,其中背包容量w最大为50。
如果还是和那一题同样的做法,复杂度将高达3000 * 10000 * 50 = 1.5 * 10 ^ 9!妥妥的会超时。那么该怎么办呢?
可以发现,n最多3000,那么可以通过DFS预处理出每个结点的父节点,深度 以及所有点对的LCA(最近公共祖先),用离线求LCA的Tarjan算法即可。
在此,我们定义LCA(x,y)为x、y在树上的最近公共祖先,且路径长度为当前结点的深度 - 需要到达的结点的深度之差 + 1,也可以理解为包含的结点数(规定自己到自己的路径长度为 1,因为结点只包括自己)。
接下来,暴力背包枚举以每个结点开始到根结点这段路每一段长度上背包容量为0 ~ 50的选取情况的最优价值,然后为了节省空间,我们只保存路径长度为2 ^ k(k = 1,2,3...)的选取信息,其中2 ^ k的最大值不超过n,即k最多只会达到11,空间复杂度完全在允许范围内。
然后对于每次询问,我们拆成两部分,x到z且包括z的一段路以及y到LCA(x,y)且不包括LCA(x,y)的路。
对每条路做一次背包,然后再对得到的背包做一次选取最优值即可。
那么如果暴力选取会超时,该怎么做才能提高效率?
还记得一开始我们记录的2 ^ k的路径信息吗?
是的,现在我们就要利用到这里面的信息了。
对于x到z长度为L的路径我们通过二进制拆分拆成数个2 ^ k个路径,这样我们就能将一开始保存下来的信息充分利用了,然后对这些路径做一次01背包,即可得到x到z的路径上背包容量分别为0~w时能选取的最优价值。同样的我们对y到LCA(x,y)的这条路径做同样处理。
最后对得到的两条路径的背包做一次有机结合,选取最优值即可。最后得到的最优值即本题的答案。
PS:当然现在的代码效率并不高,希望以后能出现更好的解决方案。
另外在此也谢谢 tsyao 神的指点。
代码如下:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define clear(A, X) memset (A, X, sizeof A) const int maxE = 6005; const int maxN = 3005; const int maxM = 55; struct Edge { int v, n; } edge[maxE]; int adj[maxN], cntE; int pre[maxN]; int n, m, V; int cost[maxN], val[maxN]; int vis[maxN]; int d[maxN][14][M]; int lca[maxN][maxN], p[maxN]; int deepth[maxN]; int f1[maxM], f2[maxM]; int find (int x) { return p[x] == x ? x : (p[x] = find (p[x])); } int Max (const int X, const int Y) { if (X > Y) return X; return Y; } void addedge (int u, int v) { edge[cntE].v = v; edge[cntE].n = adj[u]; adj[u] = cntE ++; edge[cntE].v = u; edge[cntE].n = adj[v]; adj[v] = cntE ++; } void DFS (int u, int pa) { p[u] = u; vis[u] = 1; deepth[u] = deepth[pa] + 1; for (int i = adj[u]; ~i; i = edge[i].n) { int v = edge[i].v; if (v == pa) continue; pre[v] = u; DFS (v, u); p[v] = u; } for (int i = 1; i <= n; ++ i) { int v = i; if (vis[v]) { lca[u][v] = lca[v][u] = find (v); } } } void work () { int u, v, x, y, z; clear (adj, -1); cntE = 0; clear (vis, 0); clear (deepth, 0); for (int i = 1; i <= n; ++ i) scanf ("%d%d", &cost[i], &val[i]); for (int i = 1; i < n; ++ i) { scanf ("%d%d", &u, &v); addedge (u, v); } pre[1] = 0; deepth[0] = 0; DFS (1, 0); //for (int i = 1; i <= n; ++ i) for (int j = i + 1; j <= n; ++ j) printf ("lca(%d, %d) = %d\n", i, j, lca[i][j]); for (int i = 1; i <= n; ++ i) { int k = 1, count = 1, xx = i, cur = 0, amount = 0; while (xx) { for (int j = 50; j >= cost[xx]; -- j) d[i][cur][j] = Max (d[i][cur][j], d[i][cur][j - cost[xx]] + val[xx]); if (count == k) { amount += k; if (amount > n) break; k <<= 1; ++ cur; for (int j = 50; j >= 0; -- j) d[i][cur][j] = d[i][cur - 1][j]; } xx = pre[xx]; ++ count; } } for (int i = 1; i <= m; ++ i) { scanf ("%d%d%d%d", &z, &x, &y, &V); clear (f1, 0); clear (f2, 0); int lcaa = lca[x][y]; int ans = 0; while (x != pre[z]) { int deepx = deepth[x] - deepth[z] + 1; //printf ("(%d,%d) = %d\n", x, z, deepx); int k1 = 1, amount = 0, cur = 0; while ((k1 << 1) < deepx) k1 <<= 1, ++ cur; while (1) { for (int j = V; j >= 0; -- j) for (int k = 0; k <= j; ++ k) f1[j] = Max (f1[j], f1[j - k] + d[x][cur][k]); while (++ amount < k1) x = pre[x]; x = pre[x]; if (k1 == 1) break; amount = 0; deepx = deepth[x] - deepth[z] + 1; cur = 0; k1 = 1; while ((k1 << 1) < deepx) k1 <<= 1, ++ cur; } } while (y != lcaa) { int deepy = deepth[y] - deepth[lcaa]; if (deepy == 0) break; int k1 = 1, amount = 0, cur = 0; while ((k1 << 1) < deepy) k1 <<= 1, ++ cur; while (1) { for (int j = V; j >= 0; -- j) for (int k = 0; k <= j; ++ k) f2[j] = Max (f2[j], f2[j - k] + d[y][cur][k]); while (++ amount < k1) y = pre[y]; y = pre[y]; if (k1 == 1) break; amount = 0; deepy = deepth[y] - deepth[lcaa]; cur = 0; k1 = 1; while ((k1 << 1) < deepy) k1 <<= 1, ++ cur; } } for (int j = V; j >= 0; -- j) ans = Max (ans, f1[V - j] + f2[j]); printf ("%d\n", ans); } } int main () { //freopen ("room.in", "r", stdin); //freopen ("room.txt", "w", stdout); while (~scanf ("%d%d", &n, &m)) work (); return 0; }