题解:P11220 【MX-S4-T4】「yyOI R2」youyou 的三进制数
P11220 【MX-S4-T4】「yyOI R2」youyou 的三进制数 题解
前言
给出一种实现起来较为容易的树上差分(换根 dp)的线性做法。
正文
首先我们观察题目给的四个性质,容易发现前两个性质是互逆的,后两个性质也是互逆的。
那我们考虑建图,将能够满足题目性质的一对数进行连边,连出来的就是无向边。
直接暴力建图是
我们接下来考虑一个三元组
显然可以观察到的是,对于一个满足条件的
考虑最后一个条件:同时,对于上述任意的
对应到图上,这个条件就相当于需要存在一条从
引理:对于一条确定的路径
证明:
反证法,假设一条路径
根据引理,所有
为了方便,我们定义一个点是路径上的点当且仅当存在一条从
那么对于一对
对应到圆方树上,
画个图直观感受一下(比较粗糙,见谅)。
对于一对
我们不妨以
显然在方点上没有贡献,我们只需要考虑圆点。
令
具体对于子树外的点的情况,由于对整棵树加贡献再对子树外的所有点减贡献等价于直接对整棵子树加贡献,所以这个树上差分是容易实现的。
于是这题便做完了,总时间复杂度
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll read(){
ll X = 0,r = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') r = -1,ch = getchar();
while(isdigit(ch)) X = X*10+ch-'0',ch = getchar();
return X*r;
}
const int N = 6e5+10;
int n,w,cnt;
vector<int> G[N],E[N];
int bas[N];
int dfn[N],low[N],stk[N],top,num;
void tarjan(int x,int ft){
stk[++top] = x;
low[x] = dfn[x] = ++num;
for(int y : G[x]){
if(y == ft) continue;
if(!dfn[y]){
tarjan(y,x);
low[x] = min(low[x],low[y]);
if(low[y] >= dfn[x]){
cnt++;
int z;
do{
z = stk[top--];
E[cnt].push_back(z);
E[z].push_back(cnt);
}while(z != y);
E[cnt].push_back(x);
E[x].push_back(cnt);
}
}else low[x] = min(low[x],dfn[y]);
}
}
ll siz[N],f[N];
void dfs(int x,int ft){
siz[x] = x <= n;
ll sum = 0;
for(int y : E[x]){
if(y == ft) continue;
dfs(y,x);
sum += siz[y]*siz[x];
siz[x] += siz[y];
}
if(x <= n){
for(int y : E[x]){
if(y == ft) continue;
f[y] -= (n+1-siz[y])*siz[y];
}
f[0] += sum;
f[x] += siz[x]*(n+1-siz[x]);
}
}
void dfs0(int x,int ft){
for(int y : E[x]){
if(y == ft) continue;
f[y] += f[x];
dfs0(y,x);
}
}
int main(){
n = read(); w = read();
auto add_edge = [&](int x,int y){
G[x].push_back(y);
G[y].push_back(x);
};
for(int i=1;i<=n;i++) add_edge(i/3,i);
bas[0] = 1;
for(int i=1;i<=n;i++) bas[i] = bas[i/3]*3;
for(int i=1;i<=w;i++){
if(i % 3 == 0) continue;
int x = i/3,y = i%3;
x = bas[x]*y+x;
if(x <= n) add_edge(x,i);
}
cnt = n;
tarjan(0,0);
dfs(0,0); dfs0(0,0);
for(int i=0;i<=n;i++) cout << f[i]*2 << "\n";
return 0;
}