您的位置:首页 > 其它

BZOJ 3648|寝室管理|点分治|树状数组|平衡树

2016-04-29 09:53 316 查看

题意

给出一个至多有1个环的图,求长度不超过K的路径数。

GDOI 2016 Day2 T3原题在此

题解

考虑树的情况,显然用点分治就可以搞出来了。

对于过根的路径,先处理出各深度有多少的点,再依次枚举各子树,对于各点,有其深度,问题变成了在其他子树中,与现有深度之和不超过K的有多少个,而枚举的点到根的距离是已知的t,那么就变成在之前的子树中,深度不超过K-t的点有多少个,平衡树、树状数组都可以轻易地维护。O(nlog2n)

环呢?随便删除一条边就变成树了,然后我们再考虑被删除的这条边对答案的贡献,考虑接在环上的所有子树,统计各子树后,我们就只需要扫一遍环。

我们在枚举子树的时候,每当移动一次,到之前扫过的树的距离就少了1(我们必须得经过那条被删除的边),这样看来,之前扫过的每棵树允许的深度都不一样。发现相邻的两棵子树,允许的深度差总是1,因此我们在处理树状数组的时候,把深度+1即可,每扫依次,深度就再+1,然后就可以在O(nlogn)时间内解决了。

比如对于环1-2-3-4-5-6-1,假设切断1-6这条边,从1->6的方向枚举。
K=3


枚举1,把1插入到bit[1]。我们令环上的点的深度为0。

……

枚举6,考虑之前枚举过的,到达
j=1
经过了2个点,那么层数超过
K-dis=1
的,都对答案有贡献,而且显然
dis=cir-i+j+1
,表示环上过被删除边的距离,i为枚举的点在环中的位置,也就是枚举到第i次的点。到达
j=2
经过了3个点,那么层数超过0的都计入,
j=3
经过4个点,层数超过-1的都计入……,
j=5
经过了6个点,层数超过-3的都计入。

但总不可能一一枚举j,我们发现,对于任意的i,我们枚举的j要统计的层数总是一个公差为-1的等差数列,与数列
{5,4,3,2,1}
的距离(这里指对应元素的差)为
ERROR
,我们可以利用这个性质。

对于枚举的6,到1~5的层数为
{1,0,-1,-2,-3}
(实际上负数和0没有区别)。那么5插入的深度全部+5,4插入的深度全部+4,……,层数就变成了
{2,2,2,2,2}
。那么我们在查询BIT的时候,只要查询
>=2
的总和就可以了。

对于枚举的5,到1~4的层数为
{0,-1,-2,-3}
,此时只要查询>=1的总和就可以了。

对于枚举的4,到1~3的层数为
{-1,-2,-3}
,只要查询>=0的总和即可。

假设4有个孩子(即深度为1),那么到1~3的层数就为
{-2,-3,-4}
,只要查询>=-1的总和即可。

总结一下,对于枚举的点,查询的即为
>=K-(dis<x,1>-1+level)
。level是深度。
dis<x,1>=cir-i+2


直接飞到Rank 4了。。。

不知道怎么优化常数了QwQ

树状数组版本(1.6s)

#include <cstdio>
#include <algorithm>
using namespace std;
#define adj(i,j) for(i=h[j];i;i=p[i])
#define FOR(i,j,k) for(i=j;i<=k;++i)

inline int read() {
int s=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;'0'<=ch&&ch<='9';ch=getchar())s=s*10+ch-'0';
return s*f;
}

const int N = 200005, M = N * 2, inf = 2147483647;
long long ans;
int bit
, c
, cir, n, K, sk
, top, no
;
int h
, p[M], v[M], cnt = 1;
int vis
, f
, rt, sum, sz
;

inline void add(int i, int x) {
for (; i; i -= i & -i) bit[i] += x;
}

inline int ask(int i) {
if (i < 1) i = 1;
int s = 0;
for (; i <= n + cir; i += i & -i) s += bit[i];
return s;
}

void getroot_impl(int x, int fa) {
int i;
sz[x] = 1; f[x] = 0;
adj(i, x) if (!vis[v[i]] && v[i] != fa && !no[i]) {
getroot_impl(v[i], x);
sz[x] += sz[v[i]];
f[x] = max(f[x], sz[v[i]]);
}
f[x] = max(f[x], sum - sz[x]);
if (f[x] < f[rt]) rt = x;
}

inline int getroot(int x, int fa, int sz) {
sum = sz; f[rt = 0] = inf;
getroot_impl(x, fa); return rt;
}

void dfs_depth(int x, int fa, int d) {
int i; sk[++top] = d;
adj(i, x) if (v[i] != fa && !vis[v[i]] && !no[i])
dfs_depth(v[i], x, d + 1);
}

void divide(int x) {
int l = 0, i, j;
vis[x] = 1;
adj(i, x) if (!vis[v[i]] && !no[i]) {
l = top; dfs_depth(v[i], x, 1);
FOR(j,l+1,top) ans += ask(K - sk[j] - 1);
FOR(j,l+1,top) add(sk[j], 1);
}
ans += ask(K - 1);
while (top) add(sk[top--], -1);
adj(i, x) if (!vis[v[i]] && !no[i] && sz[v[i]] >= K)
divide(getroot(v[i], x, sz[v[i]]));
}

void dfs_circle(int x, int fa) {
if (cir) return;
int i;
if (vis[x]) {
c[cir = 1] = x;
for (i = top; sk[i] != x; c[++cir] = sk[i--]);
}
vis[x] = 1; sk[++top] = x;
adj(i, x) if (v[i] != fa) dfs_circle(v[i], x);
vis[x] = 0; --top;
}

inline void work() {
int i, j;
dfs_circle(1, 0);
adj(i, c[1]) if (v[i] == c[cir]) {
no[i] = no[i ^ 1] = 1;
break;
}
divide(getroot(1, 0, n));
FOR(i,1,n) vis[i] = 0;
FOR(i,1,cir+n) bit[i] = 0;
FOR(i,1,cir) vis[c[i]] = 1;
FOR(i,1,cir) {
vis[c[i]] = 0;
dfs_depth(c[i], 0, 0);
vis[c[i]] = 1;
FOR(j,1,top) ans += ask(K - (cir - i + 1) - sk[j]);
while (top) add(sk[top--] + i, 1);
}
}

int main() {
int m, a, b, i;
n = read(); m = read(); K = read();
FOR(i,1,m) {
a = read(); b = read();
p[++cnt] = h[a]; v[cnt] = b; h[a] = cnt;
p[++cnt] = h[b]; v[cnt] = a; h[b] = cnt;
}
if (n == m) work();
else divide(getroot(1, 0, n));
printf("%lld", ans);
return 0;
}


平衡树版本(21s)

#include <cstdio>
#include <algorithm>
using namespace std;
#define adj(i,j) for(int i=h[j];i;i=p[i])
#define FOR(i,j,k) for(int i=j;i<=k;++i)

const int N = 200005, M = N * 2, inf = 2147483647;
long long ans;
int bit
, c
, cir, n, K, sk
, top, no
;
int h
, p[M], v[M], cnt = 1;
int vis
, f
, rt, sum, sz
;

void edge(int a, int b) {
p[++cnt] = h[a]; v[cnt] = b; h[a] = cnt;
}

namespace SBT {
int cnt, root;
struct Node { int l, r, s, key; } tree
;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define s(x) tree[x].s
#define key(x) tree[x].key
void reset() { cnt = root = 0; }
void r_rotate(int &t) {
int k = l(t); l(t) = r(k); r(k) = t; s(k) = s(t);
s(t) = s(l(t)) + s(r(t)) + 1;
t = k;
}
void l_rotate(int &t) {
int k = r(t); r(t) = l(k); l(k) = t; s(k) = s(t);
s(t) = s(l(t)) + s(r(t)) + 1;
t = k;
}
void maintain(int &t, bool flag) {
if (!flag) {
if(s(l(l(t))) > s(r(t)))
r_rotate(t);
else if(s(r(l(t))) > s(r(t))) {
l_rotate(l(t));
r_rotate(t);
} else return;
} else {
if(s(r(r(t))) > s(l(t)))
l_rotate(t);
else if(s(l(r(t))) > s(l(t))) {
r_rotate(r(t));
l_rotate(t);
} else return;
}
maintain(l(t), false);
maintain(r(t), true);
maintain(t, false);
maintain(t, true);
}
void insert(int &t, int v, bool flag = 1) {
if (!t) {
t = ++cnt;
key(t) = v; l(t) = r(t) = 0;
s(t) = 1;
} else {
++s(t);
insert(v < key(t) ? l(t) : r(t), v, 0);
}
if (flag) maintain(t, v >= key(t));
}
int greaterthan(int t, int w) {
if (!t) return 0;
if (key(t) >= w) return s(r(t)) + 1 + greaterthan(l(t), w);
else return greaterthan(r(t), w);
}
}

void getroot_impl(int x, int fa) {
sz[x] = 1; f[x] = 0;
adj(i, x) if (!vis[v[i]] && v[i] != fa && !no[i]) {
getroot_impl(v[i], x);
sz[x] += sz[v[i]];
f[x] = max(f[x], sz[v[i]]);
}
f[x] = max(f[x], sum - sz[x]);
if (f[x] < f[rt]) rt = x;
}

int getroot(int x, int fa, int sz) {
sum = sz; f[rt = 0] = inf;
getroot_impl(x, fa); return rt;
}

void dfs_depth(int x, int fa, int d) {
sk[++top] = d;
adj(i, x) if (v[i] != fa && !vis[v[i]] && !no[i])
dfs_depth(v[i], x, d + 1);
}

void divide(int x) {
using namespace SBT;
vis[x] = 1;
reset();
adj(i, x) if (!vis[v[i]] && !no[i]) {
dfs_depth(v[i], x, 1);
FOR(j,1,top)
ans += greaterthan(root, K - sk[j] - 1);
while (top) insert(root, sk[top--]);
}
ans += greaterthan(root, K - 1);
adj(i, x) if (!vis[v[i]] && !no[i] && sz[v[i]] >= K)
divide(getroot(v[i], x, sz[v[i]]));
}

void dfs_circle(int x, int fa) {
if (cir) return;
if (vis[x]) {
c[cir = 1] = x;
int j = top;
while (sk[j] != x) c[++cir] = sk[j--];
return;
}
vis[x] = 1; sk[++top] = x;
adj(i, x) if (v[i] != fa) dfs_circle(v[i], x);
vis[x] = 0; --top;
}

void work() {
using namespace SBT;
dfs_circle(1, 0);
adj(i, c[1]) if (v[i] == c[cir]) {
no[i] = no[i ^ 1] = 1;
break;
}
divide(getroot(1, 0, n));
FOR(i,1,n) vis[i] = 0;
reset();
FOR(i,1,cir) vis[c[i]] = 1;
FOR(i,1,cir) {
vis[c[i]] = 0;
dfs_depth(c[i], 0, 0);
vis[c[i]] = 1;
FOR(j,1,top)
ans += greaterthan(root, K - (cir - i + 1) - sk[j]);
while (top)
insert(root, sk[top--] + i, 1);
}
}

int main() {
int m, a, b;
scanf("%d%d%d", &n, &m, &K);
FOR(i,1,m) {
scanf("%d%d", &a, &b);
edge(a, b), edge(b, a);
}
if (n == m) work();
else divide(getroot(1, 0, n));
printf("%lld\n", ans);
return 0;
}


3648: 寝室管理

Description

T64有一个好朋友,叫T128。T128是寄宿生,并且最近被老师叫过去当宿管了。宿管可不是一件很好做的工作,碰巧T128有一个工作上的问题想请T64帮忙解决。

T128的寝室条件不是很好,所以没有很多钱来装修。礼间寝室仅由n-1条双向道路连接,而且任意两间寝室之间都可以互达。最近,T128被要求对一条路径上的所有寝室进行管理,这条路径不会重复经过某个点或某条边。但他不记得是哪条路径了。他只记得这条路径上有不少于k个寝室。于是,他想请T64帮忙数一下,有多少条这样的路径满足条件。

嗯…还有一个问题。由于最近有一些熊孩子不准晚上讲话很不爽,他们决定修筑一条“情报通道”,如果通道建成,寝室就变成了一个N个点N条边的无向图。并且,经过“情报通道”的路径也是合法的。T128心想:通道建成之前,T64还有一个高效的算法帮我数路径条数,但是通道建成之后,他还有办法吗?对,T64手忙脚乱,根本数不清有多少条路径。于是他找到了你。

Input

第一行为三个正整数N,M,K(2 ≤ K ≤ N),代表有n间寝室,m条边连接它们n-1 ≤ m ≤ N;m= n-1意味着“情报遁道”未被修好;m=n意味着“情报通道”已被修好),以及题目描述中的K。

接下来m行,每行两个正整数z,y,代表第x间寝室与第y间寝室之间有一条双向边。

Output

仅包含一个整数,代表经过至少K间寝室的路径条数。

Sample Input

5 5 2
1 3
2 4
3 5
4 1
5 2


Sample Output

20


HINT

N≤100000,K≤N,M=N

orz 黄学长的Program 唉?!404了?!

#include <cstdio>
#include <cstring>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
typedef long long ll;
using namespace std;
const int N = 100005, M = N * 2;
ll ans;
int h
, p[M], v[M], cnt, n, K;
int f
, sz
, dep
, sum, rt;
int stk
, top, c[N * 3], tot, cir[M], clen;
bool instk
, vis
;
void add(int a, int b) {
v[++cnt] = b; p[cnt] = h[a]; h[a] = cnt;
}
int query(int i) {
if (i < 1) return 0;
int s = 0;
for (; i; i -= i&-i) s += c[i];
return s;
}
void modify(int i, int x) {
tot += x;
for (; i <= 2 * clen + n; i += i & -i) c[i] += x;
}
void root(int x, int fa) {
f[x] = 0; sz[x] = 1;
for (int i = h[x]; i; i = p[i])
if (v[i] != fa && !vis[v[i]]) {
root(v[i], x);
sz[x] += sz[v[i]];
f[x] = max(f[x], sz[v[i]]);
}
f[x] = max(f[x], sum - sz[x]);
if (f[x] < f[rt]) rt = x;
}
int get_root(int x, int fa, int sz) {
rt = 0; sum = sz; f[0] = sz + 1;
root(x, fa); return rt;
}
void depth(int x, int fa) {
stk[++top] = dep[x];
for (int i = h[x]; i; i = p[i])
if (v[i] != fa && !vis[v[i]]) {
dep[v[i]] = dep[x] + 1;
depth(v[i], x);
}
}
void solve(int x) {
int i, j;
vis[x] = 1;
for (i = h[x]; i; i = p[i])
if (!vis[v[i]]) {
dep[v[i]] = 1;
depth(v[i], x);
FOR(j,1,top) {
if (stk[j] + 1 >= K) ++ans;
ans += tot - query(K - stk[j] - 2);
}
while (top) modify(stk[top--], 1);
}
for (i = h[x]; i; i = p[i])
if (!vis[v[i]]) {
depth(v[i], x);
while (top) modify(stk[top--], -1);
}
for (i = h[x]; i; i = p[i])
if (!vis[v[i]]) {
if (sz[v[i]] < K) continue;
solve(get_root(v[i], x, sz[v[i]]));
}
}
void circle(int x,int fa) {
if (clen) return;
stk[++top] = x; instk[x] = 1;
for (int i = h[x]; i; i = p[i])
if (v[i] != fa) {
if (instk[v[i]]) {
while (stk[top] != v[i]) cir[++clen] = stk[top--];
cir[++clen] = v[i];
} else circle(v[i], x);
}
top--; instk[x] = 0;
}
void solvecir() {
int i, j;
circle(1, 0); top = 0;
int dis = 2 * clen;
FOR(i,1,clen) vis[cir[i + clen] = cir[i]] = 1;
FOR(i,1,clen) {
vis[cir[i]] = 0;
solve(get_root(cir[i], 0, n))
vis[cir[i]] = 1;
}
FOR(i,1,n) vis[i] = 0;
FOR(i,1,clen) vis[cir[i]] = 1;
FOR(i,1,2 * clen) {
if (i > clen) {
vis[cir[i - clen]] = 0;
depth(cir[i - clen], 0);
vis[cir[i - clen]] = 1;
FOR(j,1,top) modify(stk[j] + dis + clen, -1);
FOR(j,1,top) ans += tot - query(K + dis - stk[j]);
top = 0;
}
dep[cir[i]] = 1;
vis[cir[i]] = 0;
depth(cir[i], 0);
vis[cir[i]] = 1;
while (top) modify(stk[top--] + dis, 1);
--dis;
}
}
int main() {
int i, a, b, m;
scanf("%d%d%d", &n, &m, &K);
FOR(i,1,m) {
scanf("%d%d", &a, &b);
add(a, b); add(b, a);
}
if (n == m) solvecir();
else solve(get_root(1, 0, n))
printf("%lld\n", ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: