洛谷 P8867 建造军营

传送门。

边双直接缩掉,成为一棵树。下面的【结点】都是指缩之后的。
于是可以定义一个 \(a_u\) 为,\(u\) 点内有军营的方案数,\(b_u\) 为无。

总方案数就对应着,树上每种方案的权值和。

首先考虑一个 DP,设 \(f_{u, 0/1}\) 表示 \(u\) 子树内部有或没有军营的权值和,有的话钦定 \(u\) 处必须有军营

转移的话,定义一个辅助数组 \(sum_u\),表示 \(u\) 子树内部军营的权值和。但要求,所有军营必须都与 \(u\) 连通。(意思是,如果只有 \(u\) 的一个后代设立的军营,讲道理不看守任何道路都是合法的,但是这里要求这个后代到 \(u\) 的路径上的道路都要看守)

不难发现有方程

\[f_{u, 0} \gets \prod 2f_{v, 0} \]

\[f_{u, 1} \gets \prod (2f_{v, 0} + sum_v) \]

\[sum_u \gets f_{u, 1} - f_{u, 0} + \prod (2f_{v, 0} + sum_v) \]

然后考虑怎么统计答案。
记一个 \(g_u\) 表示所有军营的 LCA 在 \(u\) 的方案数。
这就要求 \(u\) 处有军营,或 \(u\) 处没有军营且至少两个儿子的子树内有军营。

再记一个 \(sumg_u\) 表示 \(u\) 子树内 \(g\) 的和,但是这里 \(g\) 要带上【\(u\) 子树内其他位置不建造的方案数】,即一个权。

然后

\[g_u \gets f_{u,1} + b_u\left [\prod(2f_{v, 0} + sumg_v) - \prod (2f_{v, 0}) - \sum_v sumg_v \cdot \prod_{x \ne v, x \text{ is a son of u}} (2f_{v, 0})\right] \]

\(sumg\) 的转移有点难描述,不表。看代码。

时间复杂度的话,用手法离线线性求逆元的话是可以做到线性的。不过没必要。\(O(n \log V)\),其中 \(V\) 为值域。

#include <bits/stdc++.h>
using namespace std;

//#define filename "xxx" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
//#define multi_cases 1

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 1e6+2, M = 1e6+2;

const int P = 1000000007;
void vadd(int &a, int b) { a += b; if(a >= P) a -= P; }
int qpow(int a, int b) {
  int res = 1;
  while(b) {
    if(b & 1) res = 1ll * res * a % P;
    b >>= 1;
    a = 1ll * a * a % P;
  }
  return res;
} int inv(int x) { return qpow(x, P-2); }

int n, m;
pii edges[M];
struct edge {
  int v, next;
  edge(int v = 0, int next = 0) : v(v), next(next) { }
} e[M << 1];

int head[N], etot = 2;
void addedge(int u, int v) { e[etot] = edge(v, head[u]), head[u] = etot++; }

int mark[N];
int dfn[N], low[N], num;
stack<int> st;
int colour[N], ctot;
int a[N], b[N];
void Tarjan(int u, int fa) {
  dfn[u] = low[u] = ++num;
  st.push(u);
  for(int i = head[u]; i; i = e[i].next) {
    int v = e[i].v;
    if(v == fa && mark[u] == (i ^ 1)) continue;
    if(!dfn[v]) mark[v] = i, Tarjan(v, u), vmin(low[u], low[v]);
    else vmin(low[u], dfn[v]);
  }
  if(dfn[u] == low[u]) {
    ++ctot;
    while(!st.empty()) {
      int x = st.top();
      st.pop();
      colour[x] = ctot, ++a[ctot];
      if(x == u) break;
    }
  }
}

vector<int> G[N];
int pw[M];

int f[N][2];
int sum[N], sub[N];
int sz[N], coe[N];
void DFS(int u, int fa = 0) {
	f[u][1] = a[u];
	sum[u] = b[u], sub[u] = b[u];
	f[u][0] = b[u];
	sz[u] = 1;
	for(auto v : G[u]) if(v != fa) {
		DFS(v, u);
		sz[u] += sz[v];
		sub[u] = 1ll * sub[u] * sub[v] % P;
		f[u][1] = 1ll * f[u][1] * (2ll * f[v][0] + sum[v]) % P;
		sum[u] = 1ll * sum[u] * (2ll * f[v][0] + sum[v]) % P;
		f[u][0] = 1ll * f[u][0] * 2 * f[v][0] % P;
	}
	vadd(sum[u], (f[u][1] - f[u][0] + P) % P);
}

int g[N], sumg[N];
void DFS2(int u, int fa = 0) {
	int prod = 1, t = 0;
	int s = 1;
	for(auto v : G[u]) if(v != fa) {
		DFS2(v, u);
		s = 2ll * s * f[v][0] % P;
	}
	for(auto v : G[u]) if(v != fa) {
		prod = 1ll * prod * (2ll * f[v][0] + sumg[v]) % P;
		vadd(t, sumg[v] * (1ll * s * inv(2 * f[v][0]) % P) % P);
		vadd(sumg[u], 1ll * sumg[v] * (1ll * sub[u] * inv(sub[v]) % P * pw[sz[u] - sz[v] - 1] % P) % P);
	}
	g[u] = (f[u][1] + 1ll * b[u] * (prod - s - t + 2ll * P)) % P;
	vadd(sumg[u], g[u]);
}

const int root = 1;

void WaterM() {
  cin >> n >> m;
  upw(i, 1, m) {
    int u, v;
    scanf("%d%d", &u, &v);
    addedge(u, v), addedge(v, u);
    edges[i] = pii(u, v);
  }
  upw(i, 1, n) if(!dfn[i]) Tarjan(i, 0);
  
  upw(i, 1, m) {
    auto [u, v] = edges[i];
    if(colour[u] != colour[v]) {
      u = colour[u], v = colour[v];
      G[u].push_back(v), G[v].push_back(u);
    }
    else ++b[colour[u]];
  }
  pw[0] = 1;
  upw(i, 1, m) pw[i] = 2 * pw[i-1] % P;
  upw(i, 1, ctot) {
    int c = a[i], e = b[i];
    a[i] = 1ll * (pw[c] - 1) * pw[e] % P;
    b[i] = pw[e];
  }
  
  // upw(i, 1, n) cerr << colour[i] << ' ';
  // cerr << '\n';
  // upw(i, 1, ctot) cerr << a[i] << ' ' << b[i] << '\n';
  // cerr << '\n';
  
  DFS(root);
  int ans = 0, prod = 1;
  upw(i, 1, ctot) prod = 1ll * prod * b[i] % P;
  upw(i, 1, ctot) coe[i] = 1ll * prod * inv(sub[i]) % P * pw[ctot - sz[i]] % P;
  
  DFS2(root);
  
  upw(i, 1, ctot) vadd(ans, 1ll * g[i] * coe[i] % P);
  // upw(i, 1, ctot) cerr << f[i][1] << ' ' << g[i] << ' ' << sumg[i] << '\n';
  // upw(i, 1, ctot) cerr << g[i] << " * " << coe[i] << '\n';
  cout << ans << '\n';
}

signed main() {
#ifdef filename
  FileOperations();
#endif
  
  signed _ = 1;
#ifdef multi_cases
  scanf("%d", &_);
#endif
  while(_--) WaterM();
  return 0;
}
posted @ 2025-10-31 22:45  Water_M  阅读(13)  评论(0)    收藏  举报