题解:P10779 BZOJ4316 小 C 的独立集
简要题意:求仙人掌最大独立集。
是一道可以
法一:圆方树上 DP
主观难度评价(参考 LCA 的题目难度向度,
先建圆方树,然后我们发现这是一个圆方树上 经典问题,多出来的限制是环上的相邻点不能都选。
这是一个
代码如下:
/* K_crane x N_cat */
#include <bits/stdc++.h>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
const int N = 50010;
int n, m; vector < int > tr[N];
struct node {int blank, occu;}; node f[N * 2];
node operator + (node u, node v) {return {u.blank + v.blank, u.occu + v.occu};}
struct Matrix
{
int h, w, s[2][2];
inline void reset(int n, int m)
{
h = n, w = m;
memset(s, -0x3f, sizeof(s));
}
inline void unit(int n)
{
reset(n, n);
for(int i = 0; i < n; ++i) s[i][i] = 0;
}
inline void debug()
{
cerr << "debug : " << h << " x " << w << '\n';
for(int i = 0; i < h; ++i)
{
for(int j = 0; j < w; ++j)
cerr << s[i][j] << " ";
cerr << '\n';
}
}
};
inline Matrix Export(node u)
{
Matrix ret; ret.reset(2, 2);
ret.s[0][0] = u.blank;
ret.s[1][0] = u.blank;
ret.s[0][1] = u.occu;
return ret;
}
Matrix operator * (Matrix u, Matrix v)
{
Matrix res; res.reset(u.h, v.w);
for(int i = 0; i < u.h; ++i)
for(int j = 0; j < u.w; ++j)
for(int k = 0; k < v.w; ++k)
res.s[i][k] = max(res.s[i][k], u.s[i][j] + v.s[j][k]);
return res;
}
// 建圆方树
deque < int > bft[N * 2]; int fa[N * 2];
inline void init(int pos, int prt)
{
fa[pos] = prt;
for(int to : bft[pos])
{
if(to == prt) continue;
init(to, pos);
}
}
inline void treedp(int pos, int prt)
{
if(pos <= n)
{
f[pos] = {0, 1};
for(int to : bft[pos])
{
if(to == prt) continue;
treedp(to, pos);
}
// cerr << pos << " : " << f[pos].blank << " " << f[pos].occu << '\n';
}
else
{
Matrix current; current.unit(2);
for(int i = 1; i < (int)bft[pos].size(); ++i)
{
int to = bft[pos][i];
treedp(to, pos);
current = current * Export(f[to]);
}
Matrix dp[2]; for(int i = 0; i <= 1; ++i) dp[i].reset(1, 2);
dp[0].s[0][0] = 0, dp[1].s[0][1] = 0;
dp[0] = dp[0] * current, dp[1] = dp[1] * current;
f[fa[pos]] = f[fa[pos]] + (node){max(dp[0].s[0][0], dp[0].s[0][1]), dp[1].s[0][0]};
}
}
int dfn[N], low[N], sign, bcc, sta[N], top;
inline bool cmp(int x, int y) {return dfn[x] < dfn[y];}
inline void tarjan(int pos)
{
dfn[pos] = low[pos] = ++sign; sta[++top] = pos;
for(int to : tr[pos])
{
if(!dfn[to])
{
tarjan(to);
low[pos] = min(low[pos], low[to]);
if(low[to] == dfn[pos])
{
++bcc; sta[top + 1] = 0;
while(sta[top + 1] != to)
{
bft[sta[top]].push_back(n + bcc);
bft[n + bcc].push_back(sta[top]);
--top;
}
bft[pos].push_back(n + bcc);
bft[n + bcc].push_back(pos);
}
}
else low[pos] = min(low[pos], dfn[to]);
}
}
int main()
{
// freopen("text.in", "r", stdin);
// freopen("prog.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1, x, y; i <= m; ++i)
{
cin >> x >> y;
tr[x].push_back(y);
tr[y].push_back(x);
}
tarjan(1);
init(1, 0); // 预处理出圆方树上每个节点的父亲
for(int i = n + 1; i <= n + bcc; ++i)
{
sort(bft[i].begin(), bft[i].end(), cmp);
while(bft[i].front() != fa[i])
{
bft[i].push_back(bft[i].front());
bft[i].pop_front();
}
}
treedp(1, 0);
cout << max(f[1].blank, f[1].occu) << '\n';
return 0;
}
/*
*/
法二:广义串并联图上 DP
主观难度评价(参考 LCA 的题目难度向度,
直接对于每条边
原图是广义串并联图,因此我们考虑缩图。我们发现这个 DP 状态可以把并联或串联的两条边缩成一条等价的边,则直接应用广义串并联图方法即可。
代码可以参考其他题解。