bzoj3091 城市旅行
3091: 城市旅行
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2006 Solved: 655
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
1 3 2 5
1 2
1 3
2 4
4 2 4
1 2 4
2 3 4
3 1 4 1
4 1 4
Sample Output
6/1
HINT
对于所有数据满足 1<=N<=50,000 1<=M<=50,000 1<=Ai<=10^6 1<=D<=100 1<=U,V<=N
Source
分析:好题!
前两个操作说明这道题要用到LCT. 最大的问题就是如何实现操作四? 需要在splay上维护一些信息,使得能够快速统计出期望,并且支持合并和操作三的修改.
维护什么信息呢? 对于区间[x,y](路径上的,不是序号上的),其中任意两个点之间路径的点权和加起来/这个区间的点对数就是答案了. 分母很好计算:令len = (y - x + 1). 那么分母就是len * (len + 1) / 2. 关键在于如何计算分子.
对于总体求和,不能单独去考虑路径,而要考虑每一个点对答案的贡献,加起来就是答案了. 具体来说,假设区间是[1,n],那么答案就是a1 * 1 * n + a2 * 2 * (n - 1) + a3 * 3 * (n - 2) + ...... + an * n * 1.能维护出每个区间的答案就好了.
这显然是不能直接用一个数组来维护的. 令ans表示上述式子的结果. 当前区间的ans可以通过左右子区间合并再加上一些东西得到. 如果当前区间有7个数(包括根),左区间有4个数,右区间有2个数.
考虑左区间: ans[lson] = a1 * 1 * 4 + a2 * 2 * 3 + a3 * 3 * 2 + a4 * 4 * 1. 加上右区间和根后,就变成了a1 * 1 * 7 + a2 * 2 * 6 + a3 * 3 * 5 + a4 * 4 * 4. 如果令lsum = a1 * 1 + a2 * 2 + a3 * 3 + a4 * 4 = Σai * i,那么ans[lson]就是加上了lsum[lson] * (size[rson] + 1). size[rson]是右子树的大小. 这样ans就能被维护出来了.
lsum,rsum都能够用类似的方法分析得到对应的式子,并且维护.
对于操作三要怎么做呢? lsum和rsum相当于加上了d * (1 + 2 + 3 + ...... + n),可以用等差数列求和公式搞一搞. ans加上了d * (1 * n + 2 * (n - 1) + 3 * (n - 2) + ...... + n * 1). 括号里式子的结果等于n * (n + 1) * (n + 2) / 6.
细节:这道题翻转操作是要交换lsum和rsum的. 不能只打一个标记就完了. 而要即时翻转. 可以对比两份代码的不同:传送门1,传送门2.它们的区别就是一个在pushdown的时候修改当前节点,一个直接修改,并修改标记,在pushdown的时候修改子树.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 100010; ll n,m,head[maxn],to[maxn],nextt[maxn],tot = 1,a[maxn],fa[maxn]; ll sizee[maxn],sum[maxn],lsum[maxn],rsum[maxn],ans[maxn],w[maxn]; ll rev[maxn],son[maxn][2],sta[maxn],add[maxn]; void pushup(int x) { int lc = son[x][0],rc = son[x][1]; sizee[x] = sizee[lc] + 1 + sizee[rc]; sum[x] = sum[lc] + sum[rc] + w[x]; lsum[x] = lsum[lc] + w[x] * (sizee[lc] + 1) + lsum[rc] + sum[rc] * (sizee[lc] + 1); rsum[x] = rsum[rc] + w[x] * (sizee[rc] + 1) + rsum[lc] + sum[lc] * (sizee[rc] + 1); ans[x] = ans[lc] + ans[rc] + lsum[lc] * (sizee[rc] + 1) + rsum[rc] * (sizee[lc] + 1) + w[x] * (sizee[lc] + 1) * (sizee[rc] + 1); } void dfs(ll u,ll faa) { fa[u] = faa; for (ll i = head[u];i; i = nextt[i]) { ll v = to[i]; if (v == faa) continue; dfs(v,u); } } void fan(ll x) { swap(son[x][0],son[x][1]); swap(lsum[x],rsum[x]); rev[x] ^= 1; } ll cal1(ll x) { return x * (x + 1) / 2; } ll cal2(ll x) { return x * (x + 1) * (x + 2) / 6; } void Add(ll x,ll v) { w[x] += v; add[x] += v; sum[x] += v * sizee[x]; lsum[x] += v * cal1(sizee[x]); rsum[x] += v * cal1(sizee[x]); ans[x] += v * cal2(sizee[x]); } void pushdown(ll x) { if (rev[x]) { fan(son[x][0]); fan(son[x][1]); rev[x] = 0; } if (add[x]) { Add(son[x][0],add[x]); Add(son[x][1],add[x]); add[x] = 0; } } bool is_root(ll x) { return son[fa[x]][0] != x && son[fa[x]][1] != x; } bool get(ll x) { return son[fa[x]][1] == x; } void turn(ll x) { ll y = fa[x]; ll z = fa[y]; ll temp = get(x); if (!is_root(y)) son[z][son[z][1] == y] = x; fa[x] = z; son[y][temp] = son[x][temp ^ 1]; fa[son[y][temp]] = y; son[x][temp ^ 1] = y; fa[y] = x; pushup(y); pushup(x); } void splay(ll x) { ll top = 0; sta[++top] = x; for (ll y = x; !is_root(y); y = fa[y]) sta[++top] = fa[y]; for (ll i = top; i >= 1; i--) pushdown(sta[i]); ll temp; for (; !is_root(x); turn(x)) { if (!is_root(temp = fa[x])) { if (get(x) == get(temp)) turn(temp); else turn(x); } } } void Access(ll x) { ll t = 0; for (; x;t = x,x = fa[x]) { splay(x); son[x][1] = t; pushup(x); } } void Reverse(ll x) { Access(x); splay(x); fan(x); } void Link(ll x,ll y) { Reverse(x); fa[x] = y; splay(x); } void Cut(ll x,ll y) { Reverse(x); Access(y); splay(y); fa[x] = son[y][0] = 0; } ll find(ll x) { Access(x); splay(x); while (son[x][0]) x = son[x][0]; return x; } void update(ll x,ll y,ll d) { if (find(x) != find(y)) return; Reverse(x); Access(y); splay(y); Add(y,d); } ll gcd(ll a,ll b) { if (!b) return a; return gcd(b,a % b); } void query(ll x,ll y) { if (find(x) != find(y)) { printf("%d\n",-1); return; } Reverse(x); Access(y); splay(y); ll a = ans[y],b = sizee[y] * (sizee[y] + 1) / 2; ll g = gcd(a,b); printf("%lld/%lld\n",a / g,b / g); } int main() { scanf("%lld%lld",&n,&m); for (ll i = 1; i <= n; i++) { scanf("%lld",&a[i]); sizee[i] = 1; sum[i] = lsum[i] = rsum[i] = ans[i] = w[i] = a[i]; } for (int i = 1; i < n; i++) { int x,y; scanf("%d%d",&x,&y); Link(x,y); } while (m--) { ll id,u,v,d; scanf("%lld",&id); if (id == 1) { scanf("%lld%lld",&u,&v); if (find(u) == find(v)) Cut(u,v); } if (id == 2) { scanf("%lld%lld",&u,&v); if (find(u) != find(v)) Link(u,v); } if (id == 3) { scanf("%lld%lld%lld",&u,&v,&d); update(u,v,d); } if (id == 4) { scanf("%lld%lld",&u,&v); query(u,v); } } return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步