虚树
图论
假如说现在有一个很简单的 树形dp 问题,但是有多次询问,但是询问涉及的点数总和是 \(O(n)\) 级别时,就可以考虑使用虚树。
那么如何构造虚树呢?我们先把关键点按照 dfn序 来排序,然后把关键点和相邻两个点的 lca 和根节点加入虚树并去重,可以证明这样一定会包含所有的关键点的 lca。
接下来在把虚树内的节点再按照 dfn序 来排序,然后将每个点的前继与自己的 lca 向自己连边,可以证明这样一定不会经过虚树上额外的节点,然后就构造完了。
感觉虚树本质上就是一种暴力,构造虚树的办法就是先拿出来有用的点,然后再连边。
虚树的大小可以通过以下两种方式来解决:
先把每个点按照 dfn 来排序。
- 把链首尾相连,看成一个环,虚树的大小就是相邻两个点的路径长度和除以 \(2\)。
- 全部点的深度和减去相邻两个点的 lca 的长度。
容易发现方法二就是方法一的简化。
「SDOI2011」消耗战
直接建出来虚树然后大力 dp 就行了。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n;
const int N = 2.5e5 + 10;
vector<int>e[N], v[N];
int fa[N][30], minn[N][30];
int dep[N];
int dfn[N];
void dfs(int now, int ff){
fa[now][0] = ff;
dep[now] = dep[ff] + 1;
dfn[now] = ++*dfn;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
minn[now][i] = min(minn[now][i - 1], minn[fa[now][i - 1]][i - 1]);
}
for(int i = 0; i < e[now].size(); i++){
int y = e[now][i], z = v[now][i];
if(y == ff) continue;
minn[y][0] = z;
dfs(y, now);
}
}
int Flca(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int temp = dep[x] - dep[y];
int now = 0;
while(temp){
if(temp & 1){
x = fa[x][now];
}
now ++;
temp >>= 1;
}
if(x == y){
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int Vlca(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int temp = dep[x] - dep[y];
int now = 0;
int val = 1e18;
while(temp){
if(temp & 1){
val = min(val, minn[x][now]);
x = fa[x][now];
}
now ++;
temp >>= 1;
}
if(x == y){
return val;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
val = min(val, minn[x][i]);
val = min(val, minn[y][i]);
x = fa[x][i];
y = fa[y][i];
}
}
val = min(val, minn[x][0]);
val = min(val, minn[y][0]);
return val;
}
vector<int>ask;
vector<int> ne[N], nv[N];
int dp[N];
int flag[N];
void get_dp(int now, int ff){
// cerr << now << ' ' << ff << '\n';
for(int i = 0; i < ne[now].size(); i++){
int y = ne[now][i], z = nv[now][i];
if(y == ff) continue;
get_dp(y, now);
if(flag[y]){
dp[now] += z;
}
else{
dp[now] += min(dp[y], z);
}
}
}
void build(){
for(auto y: ask){
flag[y] = 1;
}
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
ask.push_back(Flca(ask[i], ask[i - 1]));
}
ask.push_back(1);
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
for(auto y: ask){
ne[y].clear();
nv[y].clear();
dp[y] = 0;
}
for(int i = 1; i < ask.size(); i++){
int lca = Flca(ask[i], ask[i - 1]);
int val = Vlca(ask[i], lca);
ne[lca].push_back(ask[i]);
nv[lca].push_back(val);
}
get_dp(1, 0);
for(auto y: ask){
flag[y] = 0;
}
cout << dp[1] << '\n';
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
for(int i = 1; i < n; i++){
int x = read(), y = read(), z = read();
e[x].push_back(y);
e[y].push_back(x);
v[x].push_back(z);
v[y].push_back(z);
}
dfs(1, 0);
int q = read();
while(q--){
int k = read();
ask.clear();
for(int i = 1; i <= k; i++){
ask.push_back(read());
}
build();
}
return 0;
}
「HEOI2014」大工程
还是很简单,直接维护最深的,最浅的,大小和总和感觉就随便做了。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n;
const int N = 1e6 + 10;
vector<int>e[N];
int fa[N][21];
int dep[N];
int dfn[N];
void dfs(int now, int ff){
fa[now][0] = ff;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
dfn[now] = ++*dfn;
dep[now] = dep[ff] + 1;
for(auto y: e[now]){
if(y == ff) continue;
dfs(y, now);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int temp = dep[x] - dep[y];
int now = 0;
while(temp){
if(temp & 1){
x = fa[x][now];
}
now ++;
temp >>= 1;
}
if(x == y){
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
vector<int> ask;
int flag[N];
int maxx[N], minn[N];
int siz[N];
ll sum[N];
vector<int>ne[N];
int ans_mn, ans_mx;
ll ans_sm;
void get_dp(int now, int ff){
if(flag[now]){
siz[now] = 1;
maxx[now] = dep[now];
minn[now] = dep[now];
sum[now] = dep[now];
}
for(auto y: ne[now]){
if(y == ff) continue;
get_dp(y, now);
ans_mn = min(minn[now] + minn[y] - 2 * dep[now], ans_mn);
ans_mx = max(maxx[now] + maxx[y] - 2 * dep[now], ans_mx);
ans_sm += sum[y] * siz[now] + sum[now] * siz[y] - 2 * dep[now] * 1ll * siz[now] * siz[y];
siz[now] += siz[y];
maxx[now] = max(maxx[now], maxx[y]);
minn[now] = min(minn[now], minn[y]);
sum[now] += sum[y];
}
}
void build(){
ans_mn = 1e9;
ans_mx = -1e9;
ans_sm = 0;
for(auto y: ask){
flag[y] = 1;
}
ask.shrink_to_fit();
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
ask.push_back(LCA(ask[i - 1], ask[i]));
}
ask.push_back(1);
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
ask.shrink_to_fit();
for(auto y: ask){
ne[y].clear();
maxx[y] = -1e9;
minn[y] = 1e9;
siz[y] = 0;
sum[y] = 0;
}
for(int i = 1; i < ask.size(); i++){
int lca = LCA(ask[i - 1], ask[i]);
ne[lca].push_back(ask[i]);
}
get_dp(1, 0);
for(auto y: ask){
flag[y] = 0;
}
ask.clear();
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
for(int i = 1; i < n; i++){
int x = read(), y = read();
e[x].push_back(y);
e[y].push_back(x);
}
for(int i = 1; i <= n; i++){
e[i].shrink_to_fit();
}
dfs(1, 0);
int q = read();
while(q--){
int k = read();
for(int i = 1; i <= k; i++){
ask.push_back(read());
}
build();
cout << ans_sm << ' ' << ans_mn << ' ' << ans_mx << '\n';
}
return 0;
}
「HNOI2014」世界树
考虑朴素的 dp 可以通过两边 dfs 的方式来解决,然后考虑硬上虚树,但有个问题是不在虚树上的点怎么做,考虑每个点做多只会有两个虚树上的点在竞争,所以就可以直接倍增来优化,然后就做完了。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n;
const int N = 300010;
vector<int>e[N];
int fa[N][22];
int dep[N];
int dfn[N];
int siz[N];
void dfs(int now, int ff){
fa[now][0] = ff;
dfn[now] = ++*dfn;
dep[now] = dep[ff] + 1;
siz[now] = 1;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(auto y: e[now]){
if(y == ff) continue;
dfs(y, now);
siz[now] += siz[y];
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y];
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
tmp >>= 1;
now ++;
}
if(x == y){
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int LCA_p(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y] - 1;
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
tmp >>= 1;
now ++;
}
return x;
}
vector<int>ask;
vector<int>ne[N];
int dis[N], id[N];
int flag[N];
int ans[N];
void get_dis_1(int now, int ff){
if(flag[now]){
dis[now] = 0;
id[now] = now;
}
for(auto y: ne[now]){
if(y == ff) continue;
get_dis_1(y, now);
int val = dis[y] + dep[y] - dep[now];
if(val < dis[now]){
dis[now] = val;
id[now] = id[y];
}
else if(val == dis[now]){
id[now] = min(id[now], id[y]);
}
}
}
void get_dis_2(int now, int ff){
for(auto y: ne[now]){
if(y == ff) continue;
int val = dis[now] + dep[y] - dep[now];
if(val < dis[y]){
dis[y] = val;
id[y] = id[now];
}
else if(val == dis[y]){
id[y] = min(id[y], id[now]);
}
get_dis_2(y, now);
}
ans[flag[id[now]]] ++;
}
void get_ans(int now, int ff){
int rr = 1;
for(auto y: ne[now]){
if(y == ff) continue;
get_ans(y, now);
rr += siz[LCA_p(y, now)];
int len = dep[y] - dep[now] - 1;
if(len == 0) continue;
int tp = LCA_p(y, now);
int ry = y;
for(int i = 20; i >= 0; i--){
int miao = fa[ry][i];
if(dep[miao] <= dep[now]) continue;
int val1 = dep[y] - dep[miao] + dis[y];
int id1 = id[y];
int val2 = dep[miao] - dep[now] + dis[now];
int id2 = id[now];
if(val1 != val2){
if(val1 < val2){
ry = fa[ry][i];
}
}
else{
if(id1 < id2){
ry = fa[ry][i];
}
}
}
ans[flag[id[now]]] += siz[tp] - siz[ry];
ans[flag[id[y]]] += siz[ry] - siz[y];
}
ans[flag[id[now]]] += siz[now] - rr;
}
void build(){
int tot = ask.size();
for(int i = 0; i < ask.size(); i++){
flag[ask[i]] = i + 1;
ans[i + 1] = 0;
}
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
ask.push_back(LCA(ask[i], ask[i - 1]));
}
ask.push_back(1);
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
for(auto y: ask){
ne[y].clear();
dis[y] = 1e18;
id[y] = 0;
}
for(int i = 1; i < ask.size(); i++) {
int lca = LCA(ask[i], ask[i - 1]);
ne[lca].push_back(ask[i]);
}
get_dis_1(1, 0);
get_dis_2(1, 0);
get_ans(1, 0);
for(int i = 0; i < ask.size(); i++){
flag[ask[i]] = 0;
}
for(int i = 1; i <= tot; i++){
cout << ans[i] << ' ';
}
cout << '\n';
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
for(int i = 1; i < n; i++){
int x = read(), y = read();
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0);
int q = read();
while(q--){
int m = read();
ask.clear();
for(int i = 1; i <= m; i++){
int x = read();
ask.push_back(x);
}
build();
}
return 0;
}
「NOI2021」庆典
挺神仙的,我们考虑到可以强连通分量来缩点,然后考虑一个事情就是我们按照每条边拓扑排序剩下的最后一条边来连边不改变连通性,所以就变成了一个森林并且方向是从祖先连向儿子的。
那么建出来虚树后是只有 \(O(1)\) 个点,然后就可以跑暴力了。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
// #define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io {
class In {
public:
template<typename T>
inline In &operator>>(T &x) {
x=0; bool f=0; char c=getchar();
while(c<'0'||c>'9') f|=(c=='-'),c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
if(c=='.') {
c=getchar(); double dot=0.1;
while(c>='0'&&c<='9') x+=(c-'0')*dot,dot*=0.1,c=getchar();
} return (f?x=-x:x),*this;
}
inline In &operator>>(char &x) {while(isspace(x=getchar())); return *this;}
inline In &operator>>(char *x) {
char c=getchar(); while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) *(x++)=c,c=getchar();
return *x=0,*this;
}
inline In &operator>>(string &x) {
char c=getchar(); x.clear();
while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) x.push_back(c),c=getchar();
return *this;
}
inline In &operator>>(In &in) { return in;}
};
class Out {
private:
char buf[35]; short dot=6,top=0;
public:
template<typename T>
inline Out &operator<<(T x) {
if(x<0) putchar('-'),x=-x;
do { buf[++top]=x%10,x/=10;} while(x);
while(top) putchar(buf[top--]|'0'); return *this;
}
inline Out &operator<<(char c) {return putchar(c),*this;}
inline Out &operator<<(string x) {for(auto c:x) putchar(c); return *this;}
inline Out &operator<<(char *x) {while(*x) putchar(*(x++)); return *this;}
inline Out &operator<<(const char *x) {while(*x) putchar(*(x++)); return *this;}
inline Out &operator<<(double x) {snprintf(buf,sizeof(buf),"%.*lf",dot,x); return (*this)<<buf;}
inline Out &operator<<(Out &out) {return out;}
inline Out &setdot(const int n) {return dot=n,*this;}
};
In fin;
Out fout;
inline Out &setdot(const int n,Out& out=fout) {return fout.setdot(n),out;}
inline In &getline(char *x,In& in=fin) {
char c=getchar();
while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) (*x++)=c,c=getchar();
return *x=0,in;
}
inline In &getline(string &x,In& in=fin) {
char c=getchar(); x.clear();
while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) x.push_back(c),c=getchar();
return in;
}
}
using namespace io;
inline int read(){
int x; fin >> x; return x;
}
int n, m, t, q;
const int N = 3e5 + 10;
vector<int>oe[N], de[N], e[N];
int odfn[N], olow[N];
int col[N];
int in[N];
int cnt;
int siz[N];
stack<int>stt;
void dfs(int now){
odfn[now] = olow[now] = ++*odfn;
in[now] = 1;
stt.push(now);
for(auto y: oe[now]){
if(!odfn[y]){
dfs(y);
olow[now] = min(olow[now], olow[y]);
}
else{
if(in[y]){
olow[now] = min(olow[now], odfn[y]);
}
}
}
if(olow[now] == odfn[now]){
cnt++;
while(stt.top() != now){
in[stt.top()] = 0;
col[stt.top()] = cnt;
siz[cnt] ++;
stt.pop();
}
stt.pop();
in[now] = 0;
col[now] = cnt;
siz[cnt] ++;
}
}
int du[N];
int fa[N][30];
int dfn[N], dep[N];
int val[N];
vector<int>rt;
int oul[N*2];
int pr[N*2];
int st[2*N][30],fir[N];
int idx=0;
auto check=[](int x,int y){return dep[x]>dep[y];};
void pre(){
pr[1]=pr[0]=0;
for(int i=2;i<2*N;i++){
pr[i]=pr[i/2]+1;
}
}
inline int LCA(int x,int y){
if(fir[x]>fir[y])swap(x,y);
int xx=fir[x],yy=fir[y];
int step=pr[yy-xx+1];
int kk1=st[xx][step];
int kk2=st[yy-(1<<(step))+1][step];
if(check(kk1,kk2)){
return kk2;
}
else{
return kk1;
}
}
void dfs_tr(int now, int ff){
dep[now] = dep[ff] + 1;
dfn[now] = ++*dfn;
oul[++idx]=now;
fir[now]=idx;
val[now] = val[ff] + siz[now];
for(auto y: e[now]){
if(y == ff) continue;
dfs_tr(y, now);
oul[++idx]=now;
}
}
vector<int>ask, ne[N], nv[N], rid[N], re[N], rv[N], rrid[N];
int vis[N], vid[N];
int qs, qt;
int tot;
int fr[N], to[N];
void build(){
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
tot = 0;
int len = ask.size();
for(int i = 1; i < len; i++){
if(LCA(ask[i - 1], ask[i]) != 0)
ask.push_back(LCA(ask[i - 1], ask[i]));
}
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
for(auto y: ask){
ne[y].clear();
nv[y].clear();
rid[y].clear();
rrid[y].clear();
re[y].clear();
rv[y].clear();
vis[y] = 0;
}
for(int i = 1; i < ask.size(); i++){
int lca = LCA(ask[i], ask[i - 1]);
if(lca == 0){
continue;
}
ne[lca].push_back(ask[i]);
nv[lca].push_back(- val[lca] + val[ask[i]] - siz[ask[i]]);
re[ask[i]].push_back(lca);
rv[ask[i]].push_back(- val[lca] + val[ask[i]] - siz[ask[i]]);
rid[lca].push_back(++tot);
rrid[ask[i]].push_back(tot);
vid[tot] = 0;
}
for(int i = 1; i <= t; i++){
int x = fr[i], y = to[i];
ne[x].push_back(y);
nv[x].push_back(0);
re[y].push_back(x);
rv[y].push_back(0);
rid[x].push_back(++tot);
rrid[y].push_back(tot);
vid[tot] = 0;
}
queue<int>rq;
rq.push(qs);
while(rq.size()){
int now = rq.front();
rq.pop();
vis[now] = 1;
for(int i = 0; i < ne[now].size(); i++){
int y = ne[now][i], id = rid[now][i];
vid[id] = 1;
if(!vis[y]){
vis[y] = 1;
rq.push(y);
}
}
}
rq.push(qt);
int ans = 0;
while(rq.size()){
int now = rq.front();
rq.pop();
if(vis[now] == 1){
ans += siz[now];
}
vis[now] = 2;
for(int i = 0; i < re[now].size(); i++){
int y = re[now][i], id = rrid[now][i];
int z = rv[now][i];
if(vid[id] == 1){
vid[id] = 2;
ans += z;
}
if(vis[y] != 2){
if(vis[y] == 1){
vis[y] = 2;
ans += siz[y];
}
rq.push(y);
}
}
}
fout << ans << '\n';
}
signed main() {
#ifdef Air
freopen("celebration.in","r",stdin);
freopen("celebration.out","w",stdout);
#endif
pre();
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
m = read();
q = read();
t = read();
for(int i = 1; i <= m; i++){
int x = read(), y = read();
oe[x].push_back(y);
}
for(int i = 1; i <= n; i++){
if(!odfn[i]){
dfs(i);
}
}
for(int i = 1; i <= n; i++){
for(auto y: oe[i]){
if(col[i] != col[y]){
de[col[i]].push_back(col[y]);
du[col[y]] ++;
}
}
}
queue<int> tq;
for(int i = 1; i <= cnt; i++){
// cerr << siz[i] << '\n';
if(!du[i]){
rt.push_back(i);
tq.push(i);
}
}
while(tq.size()){
int now = tq.front();
tq.pop();
for(auto y: de[now]){
du[y]--;
if(!du[y]){
e[now].push_back(y);
tq.push(y);
}
}
}
// for(int i = 1; i <= cnt; i++){
// if(!dep[i])
for(auto i: rt){
// cerr << i << '\n';
dfs_tr(i, 0);
}
for(int i = 1; i <= idx; i++){
st[i][0] = oul[i];
}
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<(j))-1<=idx;i++){
if(check(st[i][j-1],st[i+(1<<(j-1))][j-1])){
st[i][j]=st[i+(1<<(j-1))][j-1];
}
else{
st[i][j]=st[i][j-1];
}
}
}
while(q--){
qs = col[read()], qt = col[read()];
ask.push_back(qs);
ask.push_back(qt);
for(int i = 1; i <= t; i++){
fr[i] = col[read()];
to[i] = col[read()];
ask.push_back(fr[i]);
ask.push_back(to[i]);
}
sort(ask.begin(), ask.end());
ask.erase(unique(ask.begin(), ask.end()), ask.end());
build();
ask.clear();
}
return 0;
}
「SDOI2018」战略游戏
发现就是建出来圆方树后,答案就是虚树大小。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n, m;
const int N = 2e5 + 10;
vector<int>oe[N], e[N];
int odfn[N], olow[N];
stack<int>st;
int tot;
void dfs_ta(int now, int fr){
odfn[now] = olow[now] = ++*odfn;
st.push(now);
for(auto y: oe[now]){
if(!odfn[y]){
dfs_ta(y, now);
olow[now] = min(olow[now], olow[y]);
if(olow[y] == odfn[now]){
tot ++;
while(st.top() != y){
e[tot].push_back(st.top());
e[st.top()].push_back(tot);
st.pop();
}
st.pop();
e[tot].push_back(y);
e[y].push_back(tot);
e[tot].push_back(now);
e[now].push_back(tot);
}
}
else{
olow[now] = min(olow[now], odfn[y]);
}
}
}
int fa[N][30];
int dep[N], dfn[N];
int val[N];
void dfs(int now, int ff){
fa[now][0] = ff;
dep[now] = dep[ff] + 1;
val[now] = val[ff] + (now <= n);
dfn[now] = ++*dfn;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(auto y: e[now]){
if(y == ff) continue;
dfs(y, now);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y];
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
tmp >>= 1;
now++;
}
if(x == y) {
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
vector<int>ask, ne[N];
int siz[N];
void build(){
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
ask.push_back(LCA(ask[i], ask[i - 1]));
}
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
int ans = (ask[0] <= n);
for(int i = 1; i < ask.size(); i++){
int lca = LCA(ask[i], ask[i - 1]);
ans += val[ask[i]] - val[lca];
}
ans -= len;
cout << ans << '\n';
}
void work(){
n = read();
m = read();
tot = n;
for(int i = 0; i <= n * 2; i++){
oe[i].clear();
e[i].clear();
dfn[i] = 0;
odfn[i] = 0;
}
for(int i = 1; i <= m; i++){
int x = read(), y = read();
oe[x].push_back(y);
oe[y].push_back(x);
}
dfs_ta(1, 0);
dfs(1, 0);
int q = read();
while(q--){
int k = read();
ask.clear();
for(int i = 1; i <= k; i++){
ask.push_back(read());
}
build();
}
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int TCS = read();
while(TCS --){
work();
}
return 0;
}
「ZJOI2019」语言
发现对于每一个点,其可以选的点就是经过这个点的路径并,然后如何求这个路径并呢?
发现假如路径的两个端点所构成的虚树大小就是这个的路径并。
那么如何动态维护呢,可以搬上线段树然后做个树上差分和线段树来做,总复杂度为 \(O(n \log^2 n)\) 又有一个 log 是 lca 的 log。
点击查看代码
//これも運命じゃないか
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define double long double
#define Air
namespace io{
inline int read(){
int f = 1, t = 0; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' && ch <= '9'){t = t * 10 + ch - '0'; ch = getchar();}
return t * f;
}
inline void write(int x){
if(x < 0){putchar('-'); x = -x;}
if(x >= 10){write(x / 10);}
putchar(x % 10 + '0');
}
}
using namespace io;
int n, m;
const int N = 1e5 + 10;
vector<int>e[N];
int dep[N], dfn[N];
int id[N];
int fa[N][30];
void dfs(int now, int ff){
fa[now][0] = ff;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
dep[now] = dep[ff] + 1;
dfn[now] = ++*dfn;
id[*dfn] = now;
for(auto y: e[now]){
if(y == ff) continue;
dfs(y, now);
}
}
int LCA(int x, int y){
if(!x || !y) {
return 0;
}
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y];
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
tmp >>= 1;
now ++;
}
if(x == y) return x;
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
struct Segment{
int lc, rc;
int dat;
int cnt;
int st, ed;
#define lc(x) seg[x].lc
#define rc(x) seg[x].rc
}seg[N * 60];
int tot;
int rt[N];
void upd(int p){
seg[p].dat = seg[lc(p)].dat + seg[rc(p)].dat - dep[LCA(seg[lc(p)].ed, seg[rc(p)].st)];
seg[p].st = seg[lc(p)].st ? seg[lc(p)].st : seg[rc(p)].st;
seg[p].ed = seg[rc(p)].ed ? seg[rc(p)].ed : seg[lc(p)].ed;
}
void build(int &p){
if(!p){
p = ++tot;
}
}
void change(int p, int l, int r, int x, int v){
if(l == r){
seg[p].cnt += v;
if(seg[p].cnt){
seg[p].dat = dep[id[l]];
seg[p].st = seg[p].ed = id[l];
}
else{
seg[p].dat = 0;
seg[p].st = seg[p].ed = 0;
}
return ;
}
int mid = (l + r) >> 1;
if(x <= mid){
build(lc(p));
change(lc(p), l, mid, x, v);
}
else{
build(rc(p));
change(rc(p), mid + 1, r, x, v);
}
upd(p);
}
int merge(int p1, int p2, int l, int r){
if(!p1){
return p2;
}
if(!p2){
return p1;
}
if(l == r){
seg[p1].cnt += seg[p2].cnt;
if(seg[p1].cnt){
seg[p1].dat = dep[id[l]];
seg[p1].st = seg[p1].ed = id[l];
}
else{
seg[p1].dat = 0;
seg[p1].st = seg[p1].ed = 0;
}
return p1;
}
int mid = (l + r) >> 1;
lc(p1) = merge(lc(p1), lc(p2), l, mid);
rc(p1) = merge(rc(p1), rc(p2), mid + 1, r);
upd(p1);
return p1;
}
vector<int>del[N];
int ans = 0;
void get_ans(int now, int ff){
for(auto y: e[now]){
if(y == ff) continue;
get_ans(y, now);
merge(now, y, 1, n);
}
for(auto y: del[now]){
// cerr << "## " << y << '\n';
change(now, 1, n, dfn[y], -1);
}
int tot = seg[now].dat - dep[LCA(seg[now].st, seg[now].ed)];
ans += tot;
// if(now == 1)
// cerr << "### " << now << ' ' << tot - 1 << '\n';
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
n = read();
m = read();
tot = n;
for(int i = 1; i <= n; i++){
rt[i] = i;
}
for(int i = 1; i < n; i++){
int x = read(), y = read();
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0);
for(int i = 1; i <= m; i++){
int x = read(), y = read();
int z = LCA(x, y);
int p = fa[z][0];
// cerr << x << ' ' << y << ' ' << z << ' ' << p << '\n';
change(x, 1, n, dfn[x], 1);
change(y, 1, n, dfn[x], 1);
change(x, 1, n, dfn[y], 1);
change(y, 1, n, dfn[y], 1);
del[z].push_back(x);
del[z].push_back(y);
del[p].push_back(x);
del[p].push_back(y);
}
get_ans(1, 0);
cout << ans / 2;
return 0;
}
Bear and Chemistry
这题感觉很典啊,直接跑一个 tarjan 整出来边双,然后就直接建虚树,再在虚树上连边跑一个 tarjan,然后就直接判断每一个点在同一个边双里就行了。
第一个自己独立写出的黑。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define Air
namespace io{
inline int read(){
int f=1,t=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}
return t*f;
}
inline void write(int x){
if(x<0){putchar('-');x=-x;}
if(x>=10){write(x/10);}
putchar(x%10+'0');
}
}
using namespace io;
int n, m, q;
const int N = 300010;
vector<int> oe[N], oid[N], e[N] ;
int odfn[N], olow[N];
int cnt;
int col[N];
stack<int>st;
void dfs_ta(int now, int fr){
odfn[now] = olow[now] = ++*odfn;
st.push(now);
for(int i = 0; i < oe[now].size(); i++){
int y = oe[now][i], z = oid[now][i];
if(z == (fr ^ 1)) continue;
if(!odfn[y]){
dfs_ta(y, z);
olow[now] = min(olow[now], olow[y]);
}
else{
olow[now] = min(olow[now], odfn[y]);
}
}
if(olow[now] == odfn[now]){
cnt++;
while(st.top() != now){
col[st.top()] = cnt;
st.pop();
}
st.pop();
col[now] = cnt;
}
}
int fa[N][21];
int dfn[N], dep[N];
void dfs_tr(int now, int ff){
dfn[now] = ++*dfn;
dep[now] = dep[ff] + 1;
fa[now][0] = ff;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(auto y: e[now]){
if(y == ff) continue;
dfs_tr(y, now);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y];
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
now ++;
tmp >>= 1;
}
if(x == y) {
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int LST;
int gen(int element){
element = (element + LST) % n;
if (element == 0) {
element = n;
}
return element;
}
vector<int>ask, ne[N], nid[N], nx, ny, tsk;
int ndfn[N], nlow[N];
stack<int>nst;
int ncnt, ncol[N];
void dfs(int now, int fr){
ndfn[now] = nlow[now] = ++*ndfn;
nst.push(now);
for(int i = 0; i < ne[now].size(); i++){
int y = ne[now][i], z = nid[now][i];
if(z == (fr ^ 1)) continue;
if(!ndfn[y]){
dfs(y, z);
nlow[now] = min(nlow[now], nlow[y]);
}
else{
nlow[now] = min(nlow[now], ndfn[y]);
}
}
if(nlow[now] == ndfn[now]){
ncnt ++;
while(nst.top() != now){
ncol[nst.top()] = ncnt;
nst.pop();
}
nst.pop();
ncol[now] = ncnt;
}
}
void build(){
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
if(LCA(ask[i], ask[i - 1]) != 0)
ask.push_back(LCA(ask[i], ask[i - 1]));
}
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
ask.erase(unique(ask.begin(), ask.end()), ask.end());
ndfn[0] = nlow[0] = 0;
ncnt = 0;
for(auto y: ask){
ne[y].clear();
nid[y].clear();
ndfn[y] = 0;
nlow[y] = 0;
ncol[y] = 0;
}
int tot = 1;
for(int i = 1; i < ask.size(); i++){
int lca = LCA(ask[i - 1], ask[i]);
if(!lca) continue;
// cerr << "Vir " << lca << ' ' << ask[i] << '\n';
ne[lca].push_back(ask[i]);
nid[lca].push_back(++tot);
ne[ask[i]].push_back(lca);
nid[ask[i]].push_back(++tot);
}
for(int i = 0; i < nx.size(); i++){
int x = nx[i], y = ny[i];
// cerr << "Vir " << x << ' ' << y << '\n';
ne[x].push_back(y);
nid[x].push_back(++tot);
ne[y].push_back(x);
nid[y].push_back(++tot);
}
nx.clear();
ny.clear();
for(auto y: ask){
if(!ndfn[y]){
dfs(y, 0);
}
}
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
n = read();
m = read();
q = read();
for(int i = 1; i <= m; i++){
int x = read(), y = read();
oe[x].push_back(y);
oe[y].push_back(x);
oid[x].push_back(i * 2);
oid[y].push_back(i * 2 + 1);
}
for(int i = 1; i <= n; i++){
if(!odfn[i]){
dfs_ta(i, 0);
}
}
for(int i = 1; i <= n; i++){
// cerr << i << ' ' << col[i] << '\n';
for(auto y: oe[i]){
if(col[i] != col[y]){
e[col[i]].push_back(col[y]);
}
}
}
for(int i = 1; i <= cnt; i++){
if(!dfn[i]){
dfs_tr(i, 0);
}
}
for(int nid = 1; nid <= q; nid ++){
int nq = read(), mq = read();
for(int i = 1; i <= nq; i++){
int x = col[gen(read())];
// cerr << x << '\n';
ask.push_back(x);
tsk.push_back(x);
}
for(int i = 1; i <= mq; i++){
int x = col[gen(read())], y = col[gen(read())];
// cerr << x << ' ' << y << '\n';
ask.push_back(x);
ask.push_back(y);
nx.push_back(x);
ny.push_back(y);
}
build();
ask.clear();
bool flag = 0;
for(int i = 1; i < tsk.size(); i++){
if(ncol[tsk[i]] != ncol[tsk[0]]){
cout << "NO\n";
flag = 1;
break;
}
}
tsk.clear();
if(!flag){
cout << "YES\n";
LST += nid;
}
// cerr << '\n';
}
return 0;
}
「AHOI / HNOI2018」毒瘤
先考虑一个暴力,可以大力状压每个非树边的状态,然后就是大力 dp,总复杂度为 \(O(2^{m - n + 1} \times n)\)。
考虑优化,可以建出来虚树,然后就需要拆贡献,然后这个贡献系数的转移就是考虑一条虚树边的贡献,这个可以 \(O(n)\) 预处理,然后总复杂度就是 \(O(2^{m - n + 1} (m - n + 1) + n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define Air
namespace io{
inline int read(){
int f=1,t=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}
return t*f;
}
inline void write(int x){
if(x<0){putchar('-');x=-x;}
if(x>=10){write(x/10);}
putchar(x%10+'0');
}
}
using namespace io;
int n, m;
const int N = 1e5 + 20, MOD = 998244353;
struct Dsu{
int fa[N];
int get(int x){
return (fa[x] == x) ? (x) : (fa[x] = get(fa[x]));
}
void merge(int x, int y){
int fx = get(x), fy = get(y);
fa[fx] = fy;
return ;
}
void init(int x){
for(int i = 1; i <= x; i++){
fa[i] = i;
}
}
}dsu;
vector<int> e[N];
struct Edge{
int x, y;
};
vector<Edge>edg;
int fa[N][30];
int dep[N], dfn[N];
void dfs(int now, int ff){
dep[now] = dep[ff] + 1;
dfn[now] = ++ *dfn;
fa[now][0] = ff;
for(int i = 1; i <= 20; i++){
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for(auto y: e[now]){
if(y == ff) continue;
dfs(y, now);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
int tmp = dep[x] - dep[y];
int now = 0;
while(tmp){
if(tmp & 1){
x = fa[x][now];
}
now ++;
tmp >>= 1;
}
if(x == y) {
return x;
}
for(int i = 20; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
struct Data{
int x, y;
friend Data operator +(Data x, Data y){
return {(x.x + y.x) % MOD, (x.y + y.y) % MOD};
}
friend Data operator *(Data x, int y){
return {x.x * y % MOD, x.y * y % MOD};
}
}dat[N][2];
bool flag[N];
vector<int>ask;
void build(){
sort(ask.begin(), ask.end(), [](int x, int y){
return dfn[x] < dfn[y];
});
int len = ask.size();
for(int i = 1; i < len; i++){
ask.push_back(LCA(ask[i], ask[i - 1]));
}
for(auto y: ask){
flag[y] = 1;
}
flag[1] = 1;
}
int dp[N][2];
int ndp[N][2];
vector<int> ne[N];
vector<Data> nv[N][2];
int dfs_tr(int now, int ff){
ndp[now][0] = ndp[now][1] = 1;
int np = 0;
for(auto y: e[now]){
if(y == ff) continue;
int val = dfs_tr(y, now);
if(!val){
ndp[now][1] = ndp[now][1] * ndp[y][0] % MOD;
ndp[now][0] = ndp[now][0] * (ndp[y][0] + ndp[y][1]) % MOD;
}
else{
if(flag[now]){
ne[now].push_back(val);
nv[now][0].push_back(dat[y][0] + dat[y][1]);
nv[now][1].push_back(dat[y][0]);
}
else{
dat[now][1] = dat[y][0];
dat[now][0] = dat[y][1] + dat[y][0];
np = val;
}
}
}
if(flag[now]){
np = now;
dat[now][1] = {0, 1};
dat[now][0] = {1, 0};
}
else{
dat[now][1] = dat[now][1] * ndp[now][1];
dat[now][0] = dat[now][0] * ndp[now][0];
}
// cerr << now << '\n';
// cerr << ndp[now][1] << ' ' << ndp[now][0] << '\n';
return np;
}
bool pl[N][2];
void get_dp(int now, int ff){
dp[now][0] = ((pl[now][0]) ? 0 : ndp[now][0]);
dp[now][1] = ((pl[now][1]) ? 0 : ndp[now][1]);
// cerr << now << ' ' << ff << '\n';
for(int i = 0; i < ne[now].size(); i++){
int y = ne[now][i];
if(y == ff) continue;
get_dp(y, now);
Data dat0 = nv[now][0][i], dat1 = nv[now][1][i];
dp[now][0] = dp[now][0] * (dat0.x * dp[y][0] % MOD + dat0.y * dp[y][1] % MOD) % MOD;
dp[now][1] = dp[now][1] * (dat1.x * dp[y][0] % MOD + dat1.y * dp[y][1] % MOD) % MOD;
}
}
signed main() {
#ifndef Air
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
n = read();
m = read();
dsu.init(n);
for(int i = 1; i <= m; i++){
int x = read(), y = read();
if(dsu.get(x) == dsu.get(y)){
// cerr << x << ' ' << y << '\n';
edg.push_back({x, y});
ask.push_back(x);
ask.push_back(y);
}
else{
// cerr << x << ' ' << y << '\n';
e[x].push_back(y);
e[y].push_back(x);
dsu.merge(x, y);
}
}
// return 0;
dfs(1, 0);
build();
dfs_tr(1, 0);
int tot = edg.size();
int ans = 0;
for(int i = 0; i < (1ll << tot); i++){
for(int j = 0; j < tot; j++){
if(i & (1ll << j)){
pl[edg[j].x][1] = 1;
pl[edg[j].y][0] = 1;
}
else{
pl[edg[j].y][1] = 1;
}
}
get_dp(1, 0);
// cerr << dp[1][0] << ' ' << dp[1][1] << '\n';
ans += (dp[1][0] + dp[1][1]) % MOD;
ans %= MOD;
for(int j = 0; j < tot; j++){
pl[edg[j].x][0] = 0;
pl[edg[j].x][1] = 0;
pl[edg[j].y][0] = 0;
pl[edg[j].y][1] = 0;
}
}
cout << ans ;
return 0;
}

浙公网安备 33010602011771号