test20181004 苹果树
题意
大家的考场做法
对每个点维护子树所能达到的dfn最大值、最小值、次大值、次小值,然后就可以计算原树中每个点与父亲的连边对答案的贡献。
- 如果子树中没有边能脱离子树,断掉该边与任意一条新加的边都成立,答案就加m。
- 如果子树中只有1条边能脱离子树,只能断掉该边和那条能脱离子树的边,答案就加1。
- 如果子树中有大于等于2条边能脱离子树,那么不能通过断边使子树独立,答案不变。
时间复杂度\(O(n+m)\)
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x)
{
T data=0;
int w=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
data=10*data+ch-'0',ch=getchar();
return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;
const int MAXN=3e5+7;
int n,m;
struct Edge
{
int nx,to;
}E[MAXN<<2];
int head[MAXN],ecnt;
void addedge(int x,int y)
{
E[++ecnt].to=y;
E[ecnt].nx=head[x],head[x]=ecnt;
}
int fa[MAXN],siz[MAXN];
int dfn[MAXN],clk;
void dfs1(int x,int f)
{
fa[x]=f,siz[x]=1;
dfn[x]=++clk;
for(int i=head[x];i;i=E[i].nx)
{
int y=E[i].to;
if(y==f)
continue;
dfs1(y,x);
siz[x]+=siz[y];
}
}
int minv[MAXN],semi[MAXN];
int maxv[MAXN],semx[MAXN];
ll ans;
void dfs2(int x)
{
for(int i=head[x];i;i=E[i].nx)
{
int y=E[i].to;
if(y==fa[x])
continue;
dfs2(y);
semi[x]=min(semi[x],max(minv[x],minv[y]));
semi[x]=min(semi[x],semi[y]);
minv[x]=min(minv[x],minv[y]);
semx[x]=max(semx[x],min(maxv[x],maxv[y]));
semx[x]=max(semx[x],semx[y]);
maxv[x]=max(maxv[x],maxv[y]);
}
int out=(minv[x]<dfn[x])+(semi[x]<dfn[x])*10+
(maxv[x]>dfn[x]+siz[x]-1)+(semx[x]>dfn[x]+siz[x]-1)*10;
/* cerr<<"check "<<x<<endl;
cerr<<" minv="<<minv[x]<<" semi="<<semi[x]<<endl;
cerr<<" maxv="<<maxv[x]<<" semx="<<semx[x]<<endl;
cerr<<" ans="<<((out==0)*m+(out==1)*1)<<endl;*/
if(x!=1)
ans+=(out==0)*m+(out==1)*1;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
read(n);read(m);
for(int i=1;i<n;++i)
{
static int x,y;
read(x);read(y);
addedge(x,y);
addedge(y,x);
}
dfs1(1,0);
for(int i=1;i<=n;++i)
{
// cerr<<"dfn["<<i<<"]="<<dfn[i]<<endl;
minv[i]=maxv[i]=semi[i]=semx[i]=dfn[i];
}
for(int i=1;i<=m;++i)
{
static int x,y;
read(x);read(y);
if(dfn[x]>dfn[y])
swap(x,y);
if(dfn[x]<semi[y]) // edit 1:The first and the second must be updated like this.
{
semi[y]=dfn[x];
if(semi[y]<minv[y])
swap(semi[y],minv[y]);
}
if(dfn[y]>semx[x])
{
semx[x]=dfn[y];
if(semx[x]>maxv[x])
swap(semx[x],maxv[x]);
}
}
dfs2(1);
printf("%lld",ans);
// fclose(stdin);
// fclose(stdout);
return 0;
}
Hint
更新最大最小值以及次大次小值的时候,分为两种情况。
- 子树中的合并,就用吉司机线段树那种合并方式。
- 新增边时候的修改,必须用swap形式的方式修改。
有同学写vector存图被卡了,另外我的代码最慢测试点只跑了0.2s,以后要借鉴这种常数小的写法。
标解
我们发现如果一条树边有贡献则树上最多只有一条新加的边覆盖了它,那么我们只要写一下LCA(最近公共祖先算法)然后树上差分一下,被新边覆盖了恰好一次的树边就会有1 的贡献,而没有被覆盖的有m 的贡献,直接统计一下就好了。
复杂度:\(O(n log n)\) 或\(O(n)\)(取决于 LCA 算法的复杂度),期望得分:90~100分。
//waz
#include <bits/stdc++.h>
using namespace std;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) ((int)((x).size()))
typedef pair<int, int> PII;
typedef vector<int> VI;
typedef long long int64;
typedef unsigned int uint;
typedef unsigned long long uint64;
#define gi(x) ((x) = F())
#define gii(x, y) (gi(x), gi(y))
#define giii(x, y, z) (gii(x, y), gi(z))
int F()
{
char ch;
int x, a;
while (ch = getchar(), (ch < '0' || ch > '9') && ch != '-');
if (ch == '-') ch = getchar(), a = -1;
else a = 1;
x = ch - '0';
while (ch = getchar(), ch >= '0' && ch <= '9')
x = (x << 1) + (x << 3) + ch - '0';
return a * x;
}
const int N = 3e5 + 10;
int n, m;
struct edge
{
int to;
edge *next;
} e[N << 1], *et = e, *la[N];
void add(int u, int v)
{
*++et = (edge) {v, la[u]}, la[u] = et;
*++et = (edge) {u, la[v]}, la[v] = et;
}
int siz[N], son[N], fa[N], dep[N];
void dfs1(int u)
{
siz[u] = 1;
dep[u] = dep[fa[u]] + 1;
for (edge *it = la[u]; it; it = it -> next)
{
int v = it -> to;
if (v == fa[u]) continue;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (siz[son[u]] < siz[v])
son[u] = v;
}
}
int top[N];
void dfs2(int u, int tp)
{
top[u] = tp;
if (son[u]) dfs2(son[u], tp);
for (edge *it = la[u]; it; it = it -> next)
{
if (it -> to == son[u] || it -> to == fa[u]) continue;
dfs2(it -> to, it -> to);
}
}
int lca(int u, int v)
{
for (; top[u] != top[v]; dep[top[u]] > dep[top[v]] ? u = fa[top[u]] : v = fa[top[v]]);
return dep[u] < dep[v] ? u : v;
}
int cover[N];
void dfs3(int u)
{
for (edge *it = la[u]; it; it = it -> next)
{
if (it -> to == fa[u]) continue;
dfs3(it -> to);
cover[u] += cover[it -> to];
}
}
int main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
gii(n, m);
for (int i = 1; i < n; ++i)
{
int u, v;
gii(u, v);
add(u, v);
}
dfs1(1);
dfs2(1, 1);
for (int i = 1; i <= m; ++i)
{
int s, t;
gii(s, t);
++cover[s], ++cover[t];
cover[lca(s, t)] -= 2;
}
dfs3(1);
long long ans = 0;
for (int i = 2; i <= n; ++i)
if (cover[i] == 1) ++ans;
else if (!cover[i]) ans += m;
printf("%lld\n", ans);
return 0;
}
静渊以有谋,疏通而知事。