@2368860385
2020-11-07T03:00:51.000000Z
字数 4722
阅读 176
衢州
没见过这种hash。。。
于是暴力,后面链的情况写了,过了自己造的样例,然后先交了一下。之后写对拍,然后发现链的情况有问题,然后改,然后就忘记在交一遍了。。
5分感觉好少,于是想30分的做法。。。
于是想到是先是一棵树,只有几十个双联通分量。对于非双联通分量里的点都是-1,叶子节点特判一下,强联通分量里的点需要处理一下。
处理好麻烦!!!!!
为什么这样想好像对,写不对?细节太多,复杂度还不大正确,出题人不是这样想的。
5分,直接枚举,还有地方没处理好。
对一棵子树求出 hash ,放到set里,然后在另一棵树里查询。
可以开n个set,表示大小为n的子树。
当前子树的大小为k,那么对于另一棵子树中,所有对应的点,的lca,以这个lca为根的子树中,大小为看k。
可以放到dfs序上处理。
集合hash,只查询元素有没有,不需要查询顺序。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#include<cctype>
#include<vector>
#include<set>
using namespace std;
typedef long long LL;
inline int read() {
int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
}
const int N = 200010;
vector<int>T[N];
LL key[N], Hash[N];
int siz[N], Ans;
set<LL>s[N];
void init(int x) {
LL a = (rand() << 15) | rand();
LL b = (rand() << 15) | rand();
key[x] = a * b;
}
void dfs1(int u,int pa) {
siz[u] = 1, Hash[u] = key[u];
int sz = T[u].size();
for (int i=0; i<sz; ++i) {
int v = T[u][i];
if (v == pa) continue;
dfs1(v,u);
siz[u] += siz[v];
Hash[u] ^= Hash[v];
}
if (u == 1) return ;
s[siz[u]].insert(Hash[u]);
}
void dfs2(int u,int pa) {
siz[u] = 1, Hash[u] = key[u];
int sz = T[u].size();
for (int i=0; i<sz; ++i) {
int v = T[u][i];
if (v == pa) continue;
dfs2(v,u);
siz[u] += siz[v];
Hash[u] ^= Hash[v];
}
if (u == 1) return ;
if (s[siz[u]].count(Hash[u])) Ans ++;
}
int main() {
int n = read();
for (int i=1; i<=n; ++i) init(i);
for (int i=1; i<n; ++i) {
int u = read(),v = read();
T[u].push_back(v);
T[v].push_back(u);
}
dfs1(1,0);
for (int i=1; i<=n; ++i) T[i].clear();
for (int i=1; i<n; ++i) {
int u = read(),v = read();
T[u].push_back(v);
T[v].push_back(u);
}
dfs2(1,0);
cout << Ans;
return 0;
}
题意:对于每个点i,求出从这个点出发,选一条边,然后遍历完所有点(中途不能回到i,每条边可以走多次),再从选的边回到这个点的经过的最大的边最小是多少。
m=n+60,求出MST,枚举每个点被删除后,然后再枚举剩余60条边,加入。
先求一遍MST,然后从小到大,枚举非树边。
考虑返祖边:u->v,边权为w,这条边,那么所有fa[u]->son[v]的点(就是这条路径经过的点中,不包含uv的点),会对更新这些点(称为更新点,假设i是这些点中的一个,因为去掉这个点i后,形成了degree[i]个联通块,需要把这些联通块在连起来,而这条边就会使 i的fa所在的联通块 和 u所在的联通块合起来)。
如何更新贡献:对于一个 路径点(路径u->v上的点)i,其父节点p(要求p一定是更新点,所以不能是v),由于去掉p后,它的子节点i,可以连向p的fa所在的联通块,所以更新ans[p]=w。因为i对p产生了贡献,所以称i为贡献点。以此类推,就可以更新完所有的更新点。
i做为贡献点,更新了p,之后i不会再成为贡献点去更新p了。因为去掉p后形成的联通块中,i所在的联通块,用u->v这条边连接是最优的,所以就用u->v连接了。所以如果再有路径可以让i对p产生贡献,可以直接跳到p。而p也许已经做过贡献点了,又跳到了p的fa……所以用并查集维护,将做过贡献点的指向最近的一个没做过贡献点。
合并:u就是贡献点,v是更新点(u相当于i,v相当于p)。
void Merge(int u,int v) {
u = find(u), v = find(v);
if (u != v) fa[u] = v; // 顺序不能反
}
横插边:u->v,可以拆成u->lca,v->lca两条返租边来做,lca也是更新点,而拆成的两路径不能更新lca,所以最后判断一下。
核心代码,路径u->v,权值w,u是贡献点,p是更新点。
while (deth[p] > deth[v]) {
ans[p] = w; dgr2[p]++; Merge(u, p);
u = find(u), p = f[u][0];
}
统计答案:如果一个点更新了degree[i]-1次,那么说明将i去掉后,可以形成联通块,答案是ans[i],否则答案是-1。特判一下叶子节点。
每个点最多做一次贡献点,如果lca使用求的话,复杂度是
tips:撤销操作,用一个栈记录操作序列。
tipS树套树:log,维护外面树对套树的映射。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cctype>
#include<vector>
using namespace std;
typedef long long LL;
inline int read() {
int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1;
for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f;
}
const int N = 1000100;
struct Edge{
int u, v, w;
Edge() {}
Edge(int a,int b,int c) {u = a, v = b, w = c;}
bool operator < (const Edge &A) const {
return w < A.w;
}
}e[N];
int Enum, tot;
int g[N], fa[N], f[N][21], dgr1[N], dgr2[N], deth[N], ans[N];
vector<int>T[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int Kruskal(int n,int m) {
for (int i=1; i<=n; ++i) fa[i] = i;
int cnt = 0, ans = -1;
sort(e + 1, e + m + 1);
for (int i=1; i<=m; ++i) {
int u = e[i].u, v = e[i].v;
int fu = find(u), fv = find(v);
if (fu == fv) {
g[++tot] = i; continue;
}
++cnt; fa[fu] = fv; ans = e[i].w;
dgr1[u] ++; dgr1[v] ++;
T[u].push_back(v); T[v].push_back(u);
}
if (cnt == n - 1) return ans;
return -1;
}
void dfs(int u,int fa) {
deth[u] = deth[fa] + 1;
for (int sz=T[u].size(), i=0; i<sz; ++i) {
int v = T[u][i];
if (v == fa) continue;
f[v][0] = u;
dfs(v, u);
}
}
int Lca(int u,int v) {
if (deth[u] < deth[v]) swap(u, v);
int d = deth[u] - deth[v];
for (int i=20; i>=0; --i)
if (d & (1 << i)) u = f[u][i];
if (u == v) return u;
for (int i=20; i>=0; --i)
if (f[u][i] != f[v][i])
u = f[u][i], v = f[v][i];
return f[u][0];
}
void Merge(int u,int v) {
u = find(u), v = find(v);
if (u != v) fa[u] = v;
}
int main() {
int n = read(), m = read();
for (int i=1; i<=m; ++i) {
int u = read(), v = read(), w = read();
e[++Enum] = Edge(u, v, w);
}
int Minans = Kruskal(n, m);
if (Minans == -1) {
cout << -n; return 0;
}
dfs(1, 0);
for (int j=1; j<=20; ++j)
for (int i=1; i<=n; ++i)
f[i][j] = f[f[i][j-1]][j-1];
for (int i=1; i<=n; ++i) fa[i] = i;
for (int i=1; i<=tot; ++i) {
int u = e[g[i]].u, v = e[g[i]].v, w = e[g[i]].w, p, tu, tv;
if (deth[u] < deth[v]) swap(u, v);
int x = Lca(u, v);
if (x == v) {
u = find(u), p = f[u][0];
while (deth[p] > deth[v]) {
ans[p] = w; dgr2[p]++; Merge(u, p);
u = find(u), p = f[u][0];
}
}
else {
tu = u, tv = v;
u = find(u), p = f[u][0];
while (deth[p] > deth[x]) {
ans[p] = w; dgr2[p]++; Merge(u, p);
u = find(u), p = f[u][0];
}
v = find(v), p = f[v][0];
while (deth[p] > deth[x]) {
ans[p] = w; dgr2[p]++; Merge(v, p);
v = find(v), p = f[v][0];
}
u = find(tu), v = find(tv);
if (u != v) {
if (deth[u] < deth[v]) swap(u, v);
ans[x] = w; dgr2[x]++; Merge(u, v);
}
}
}
LL Ans = 0;
for (int i=1; i<=n; ++i) {
if (dgr1[i] == 1) Ans += Minans;
else if (dgr2[i] < dgr1[i] - 1) Ans --;
else Ans += max(Minans, ans[i]);
}
cout << Ans;
return 0;
}