BZOJ 5477: 星際穿越


當初隨便出的一道 思博題 竟然被交換到了八中 QAQ

然后就上了 BZOJ 。。。作為原作者還是把原來寫的詳細題解放出來吧 qwq

題意

\(n\) 個點的數,每個點初始有權值 \(v_i\) ,需要支持 \(m\) 次操作。

動態單點加權值,查詢子樹內點對的距離和。

題解

5pts

觀察到第 \(9\) 個點,只有修改,沒有詢問。只需要正確打開文件並且不輸出任何字符即可。

(注意暴力不能 \(RE\) 或者 \(MLE\)\(TLE\) 才能 艱難地 拿到這個分)

15pts

對於 \(n, m \le 50\) 的點,每次直接修改點權。

詢問的時候暴力枚舉子樹中的點對,然后跳 \(Lca\) 計算距離。

復雜度是 \(O(mn^3)\) 的。

30pts

發現暴力枚舉點對的時候不需要重新跳 \(Lca\) ,直接從每個點 \(Bfs\) or \(Dfs\) 即可。

復雜度是 \(O(mn^2)\) 的。

45pts

考慮把暴力這顆子樹弄出來,就變成求樹上兩兩點對帶權距離和的經典問題。
如果工業一點就可以寫個點分治,復雜度就是 \(O(mn \log n)\) 的,常數優秀應該可以得到這些分。

但其實沒有這么麻煩,如果是邊權的話,每個邊被多少條路徑經過,其實就是兩邊子樹 \(size\) (子樹大小)的乘積,也就是 \(size * (tot - size + 1)\)

但這題權值是在點上的,所以要枚舉它每一個兒子順次計算,就是每個點的 \(size\) 和前面的 \(size\) 之和的乘積,最后加上子樹內外的 \(size\) 之積就行了。然后復雜度就是 \(O(nm)\) 的。

還有一種一遍 \(dp\) 一遍換根的做法就不多贅述了,復雜度也是 \(O(nm)\) 的。

55pts

其實剛剛那個 \(O(nm \log n)\) 或者 \(O(nm)\) 的算法也可以通過 \(10, 11\) 號點的...

60pts

但對於只有詢問的點,應該是有更好的做法,利用兩點距離 \(dis_{a, b} = dep_a + dep_b - dep_{lca(a, b)} \times 2 + v_{lca(a, b)}\) 這個經典計算方式。

考慮每個點的 \(dep\) 計算了多少次,以及它作為 \(lca\) 時計算了多少次 \(dep\)\(v\)

我們推導式子:

\[\begin{aligned} ans &= \sum_{u \in child_p} \sum_{v \in child_p, v \ge u} dep_u + dep_v - dep_{lca(a, b)} * 2 + v_{lca(a, b)} \\ &= (\sum_{u \in child_p} dep_u) \times (size_p + 1) + \sum_{u \in child_p} (v_u - dep_u * 2) * coef_u \end{aligned} \]

此處 \(coef_u\)\(u\) 作為 \(lca\) 出現的次數。至於求這個,可以依次考慮它的每個兒子,系數就是每個兒子的 \(sz\) 乘上前面所有 \(sz\) 的和(一開始要算上 \(u\) 點)。

我們用 \(Dfs\) 預處理就行了,記下當前的 \(\displaystyle \sum_{u \in child_p} dep_u\) 的值,以及 \(\displaystyle \sum_{u \in child_p} (v_u - dep_u * 2) * coef_u\) 即可在 \(O(n)\) 的時間里預處理出所有點的答案。

80pts

\(u_i = v_i - 1\) :直接考慮每個點被多少個區間覆蓋即可,用線段樹支持動態修改和查詢。

\(u_i = 1\) :分類討論即可。詢問的時候 \(p = 2\)\(u \not = 1\) 的時候直接輸出 \(v_p\)\(u = 1\) 的時候也可以十分輕易地直接維護答案。

這些點只是為了湊部分分的

100pts

方法一

至於正解,我們考慮動態維護前面 \(60pts\) 的式子。

我們發現每次只需要動態查詢子樹的 \(\sum dep_u\) (帶權深度)的和,以及 \(\displaystyle \sum_{u \in child_p} (v_u - dep_u * 2) * coef_u\) 就行了。

每次修改單點權值,等價於修改子樹內所有點的帶權深度和,我們用線段樹支持子樹修改就行了,然后詢問的時候就子樹詢問。

至於 \(*~coef_u\) ,我們對於線段樹每個區間維護 \(coef\) 的和,每次給線段樹區間加的時候,把 \(sum\) 加上 \(coef \times x\) 就行了,復雜度就是 \(O(m \log n)\) 的。

方法二

考慮前面 \(45pts\) 其中的一種做法,考慮一個點被多少條路徑穿過。

我們先假設只詢問全樹,那么可以用樹狀數組維護每個點的系數。那么就可以直接單點修改,區間查詢就行了。

如果是詢問子樹的話,也是很簡單的,我們減去經過子樹 \(u\) 內點的路徑的多余貢獻就行了。具體來說,就是子樹 \(u\) 中每個點的 \(size\) 乘上子樹 \(u\) 外的點數 \(n - size_u\) 就行了。

同樣這個也可以用樹狀數組實現,十分的好寫好調。

一些有意思的東西

這題應該是一道原創 送分 題,其實思路十分的簡單,體現了出題人的良心。

考察了對於代碼的實現以及對於數據結構的實現。

就是代碼實現略微有點細節,利用了一個差分的小 \(trick\)

在出完這道題后,找 Hometown 驗題的時候,他告訴我了方法二,簡單好寫常數小,發現 \(std\) 又雙叒叕被踩了。。。 果然我只能出思博題啊!

代碼

方法一

這是出題人一開始想到的一個 sb 方法,常數大,還難寫。。

/**************************************************************
    Problem: 5477
    User: zjp_shadow
    Language: C++
    Result: Accepted
    Time:8816 ms
    Memory:67256 kb
****************************************************************/
 
#include <bits/stdc++.h>
 
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back
 
using namespace std;
 
template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}
 
inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}
 
typedef long long ll;
 
const int N = 3e5 + 1e3, Mod = 1e9 + 7;
 
vector<int> G[N]; int id[N];
 
inline int Plus(int a, int b) {
    return (a += b) >= Mod ? a - Mod : a;
}
 
#define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
 
template<int Maxn>
struct Segment_Tree {
 
    int coef[Maxn], sumv[Maxn], tag[Maxn];
 
    inline void Modify(int o, int uv) {
        tag[o] = Plus(tag[o], uv);
        sumv[o] = (sumv[o] + 1ll * coef[o] * uv) % Mod;
    }
 
    inline void Push_Down(int o) {
        if (tag[o])
            Modify(o << 1, tag[o]), Modify(o << 1 | 1, tag[o]), tag[o] = 0;
    }
 
    inline void Push_Up(int o) {
        sumv[o] = Plus(sumv[o << 1], sumv[o << 1 | 1]);
    }
 
    void Build(int o, int l, int r, int *base, int *cur) {
        if (l == r) { sumv[o] = 1ll * (coef[o] = base[id[l]]) * cur[id[l]] % Mod; return ; }
        int mid = (l + r) >> 1;
        Build(lson, base, cur); Build(rson, base, cur);
        Push_Up(o); coef[o] = Plus(coef[o << 1], coef[o << 1 | 1]);
    }
 
    void Update(int o, int l, int r, int ul, int ur, int uv) {
        if (ul <= l && r <= ur) { Modify(o, uv); return ; }
        int mid = (l + r) >> 1; Push_Down(o);
        if (ul <= mid) Update(lson, ul, ur, uv);
        if (ur > mid) Update(rson, ul, ur, uv); Push_Up(o);
    }
 
    int Query(int o, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) return sumv[o];
        int mid = (l + r) >> 1, res = 0; Push_Down(o);
        if (ql <= mid) res = Plus(res, Query(lson, ql, qr));
        if (qr > mid) res = Plus(res, Query(rson, ql, qr));
        Push_Up(o); return res;
    }
 
};
 
#undef lson
#undef rson
 
Segment_Tree<N << 2> T1, T2, T3;
 
int coef[N], dfn[N], efn[N], sz[N], dep[N];
 
void Dfs_Init(int u = 1, int fa = 0) {
    static int clk = 0;
    dep[u] = dep[fa] + 1;
    id[dfn[u] = ++ clk] = u;
 
    coef[u] = sz[u] = 1;
    Rep (i, G[u].size()) {
        int v = G[u][i];
        if (v != fa) {
            Dfs_Init(v, u);
            coef[u] = (coef[u] + 1ll * sz[u] * sz[v]) % Mod;
            sz[u] += sz[v];
        }
    }
    efn[u] = clk;
}
 
int main () {
 
    //freopen ("transport.in", "r", stdin);
    //freopen ("transport.out", "w", stdout);
 
    int n = read(), m = read();
 
    For (i, 1, n - 1) {
        int u = read(), v = read();
        G[u].pb(v); G[v].pb(u);
    }
 
    Dfs_Init();
 
    int I[N]; For (i, 1, n) I[i] = 1;
 
    T1.Build(1, 1, n, I, dep);
    T2.Build(1, 1, n, coef, dep);
    T3.Build(1, 1, n, coef, I);
 
    For (i, 1, m) {
        int opt = read(), pos = read();
        if (opt == 1) {
            int val = read();
            T1.Update(1, 1, n, dfn[pos], efn[pos], val);
            T2.Update(1, 1, n, dfn[pos], efn[pos], val);
            T3.Update(1, 1, n, dfn[pos], dfn[pos], val);
        } else {
 
            long long ans = 
                1ll * T1.Query(1, 1, n, dfn[pos], efn[pos]) * (sz[pos] + 1)
                - T2.Query(1, 1, n, dfn[pos], efn[pos]) * 2ll
                + T3.Query(1, 1, n, dfn[pos], efn[pos]);
 
            printf ("%lld\n", (ans % Mod + Mod) % Mod);
        }
    }
 
    return 0;
}

方法二

簡單的樹狀數組解法 QAQ

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define pb push_back

using namespace std;

typedef long long ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
	int x(0), sgn(1); char ch(getchar());
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
	for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
	return x * sgn;
}

const int N = 3e5 + 1e3, Mod = 1e9 + 7;

vector<int> G[N]; 

int n, dfn[N], efn[N], sz[N], coef[N];

inline int Plus(int a, int b) {
	return (a += b) >= Mod ? a - Mod : a;
}

#define lowbit(x) (x & -x)
template<int Maxn>
struct Fenwick_Tree {

	int sumv[Maxn];

	inline void Update(int pos, int uv) {
		for (; pos <= n; pos += lowbit(pos)) 
			sumv[pos] = Plus(sumv[pos], uv);
	}

	inline int Query(int pos) {
		int res = 0;
		for (; pos; pos -= lowbit(pos))
			res = Plus(res, sumv[pos]);
		return res;
	}

	inline int Query(int l, int r) {
		return Query(r) - Query(l - 1);
	}

};

Fenwick_Tree<N> T1, T2;

void Dfs_Init(int u = 1, int fa = 0) {
	static int clk = 0;
	dfn[u] = ++ clk;

	sz[u] = coef[u] = 1;
	Rep (i, G[u].size()) {
		int v = G[u][i];
		if (v != fa) {
			Dfs_Init(v, u); 
			coef[u] = (coef[u] + 1ll * sz[u] * sz[v]) % Mod;
			sz[u] += sz[v];
		}
	}
	coef[u] = (coef[u] + 1ll * sz[u] * (n - sz[u])) % Mod;

	T1.Update(dfn[u], coef[u]);
	T2.Update(dfn[u], sz[u]); efn[u] = clk;
}

int main () {

////freopen ("transport.in", "r", stdin);
////freopen ("transport.out", "w", stdout);

	n = read(); int m = read();

	For (i, 1, n - 1) {
		int u = read(), v = read();
		G[u].pb(v); G[v].pb(u);
	}

	Dfs_Init();

	For (i, 1, m) {
		int opt = read(), pos = read();
		if (opt == 1) {
			int val = read();
			T1.Update(dfn[pos], 1ll * val * coef[pos] % Mod);
			T2.Update(dfn[pos], 1ll * val * sz[pos] % Mod);
		} else {
			long long ans = 
				T1.Query(dfn[pos], efn[pos]) 
				- 1ll * T2.Query(dfn[pos], efn[pos]) * (n - sz[pos]);
			printf ("%lld\n", (ans % Mod + Mod) % Mod);
		}
	}

	return 0;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM