poj 3321
Intro
题目地址 http://poj.org/problem?id=3321, 我的遍历算法提交上去居然 TLE 了。poj 网站也没有告诉我用了多少秒,这样我就没办法知道我和别人的差距。也找不到测试数据集来自测,只好写个 python 程序来生成测试数据集。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Queue
import random
"""
print 3
print "1 2"
print "1 3"
print "3"
print "Q 1"
print "C 2"
print "Q 1"
"""
MAX_FORK = 100000
MAX_QUERY = 100000
def build_balanced_fork():
# 这样建的树是 平衡树
q = Queue.Queue()
q.put(1)
fork_num = 0
min_b = 2
while fork_num < MAX_FORK:
s = q.get()
print "%d %d\n%d %d" % (s, min_b, s, min_b+1)
fork_num += 2
q.put(min_b)
q.put(min_b+1)
min_b = min_b + 2
def build_unbalanced_fork():
q = Queue.Queue()
q.put(1)
fork_num = 0
min_b = 2
while fork_num < MAX_FORK:
s = q.get()
if q.qsize() and random.choice([True, False]):
continue
print "%d %d\n%d %d" % (s, min_b, s, min_b+1)
fork_num += 2
q.put(min_b)
q.put(min_b+1)
min_b = min_b + 2
def build_query():
for i in range(0, MAX_QUERY):
operation = random.choice(['C', 'Q'])
number = random.randint(1, MAX_QUERY)
print "%s %d" % (operation, number)
if __name__ == '__main__':
print MAX_FORK+1
build_unbalanced_fork()
print MAX_QUERY
build_query()
这里的 markdown 编辑器没有预览功能,好难用啊!
一开始, 我只实现了 build_balanced_fork, build_query 两个函数。 build_balanced_fork 函数建立的是个平衡树。 在平衡树树上, 我的遍历修改查询程序和采用树状数组的程序(用的是 hzwer 的代码 http://hzwer.com/8114.html )所花费时间差不多。
用 build_unbalanced_fork 建非平衡树测试集。在非平衡树上,我的程序所用的时间是 hzwer 程序的 几十倍。经过修改后, hzwer 的程序还是比我的快 3 倍。考虑到他的程序用了位运算,而我的程序没有使用。这也是他程序比我快的一个原因。
这样看来, 采用树状数组会减少程序运行时间。
# include <vector>
# include <iostream>
# include <memory.h>
using namespace std;
long long count = 0;
const int maxn = 100010;
int bit[maxn];
int tstart[maxn] = {0};
int tend[maxn] = {0};
int n = 0;
vector<vector<int>> tree(maxn+1);
int lowbit(int x) {
return x&-x;
}
int sum(int i) {
int res = 0;
while (i > 0) {
res += bit[i];
i -= lowbit(i);
}
return res;
}
void add(int i, int x) {
while (i <= n) {
bit[i] += x;
i += lowbit(i);
}
}
int build_node(int i) {
tstart[i] = count;
count++;
add(count, 1);
for (int j=0; j< tree[i].size(); j++) {
build_node(tree[i][j]);
}
tend[i] = count;
return 0;
}
int main(int argc, char *argv[]) {
int m = 0;
int m_index = 0;
int delta = 0;
int parent, son;
long long op = 0;
scanf("%d", &n);
vector<int> ans;
vector<int> has_apple(n+1, 1);
memset(bit, 0, sizeof(int)*maxn); // 一开始没有 memset 初始化,导致程结果总是不对!
for (int i=1; i<n; i++) {
scanf("%d%d", &parent, &son);
tree[parent].push_back(son);
}
build_node(1);
scanf("%d", &m);
while (m--) {
char cmd[2];
int now_p = 0;
scanf("%s%d", cmd, &m_index);
if (cmd[0]== 'C') {
delta = has_apple[m_index]? -1: 1;
has_apple[m_index] ^=1;
add(tstart[m_index]+1, delta);
}
else {
;
#ifndef DEBUG
ans.push_back(sum(tend[m_index]) - sum(tstart[m_index]));
#endif
}
}
#ifdef DEBUG
printf("%lld\n", op);
#else
for (int i=0; i< ans.size(); i++)
printf("%d\n", ans[i]);
#endif
}
经过我不懈的努力,总算用树状数组把时间降下来了。话说位运算就是快。现在来分析一下为什么之前的程序会 TLE 到底性能瓶颈在哪里?
# include <vector>
# include <iostream>
using namespace std;
vector <long long> sum_node (100010, 0);
long long build_node(int i, vector<vector<int>> &tree) {
long long ans = 1;
for (int j = 0; j < tree[i].size(); j++)
ans += build_node(tree[i][j], tree);
sum_node[i] = ans;
return ans;
}
int main(int argc, char *argv[]) {
int n = 0, m = 0;
int parent, son;
int delta = 0;
int deltas = [1, -1];
long long op = 0;
scanf("%d", &n);
vector<long long> ans;
vector<int> has_apple(n+1, 1);
vector<int> father_node(n+1, -1);
vector<vector <int>> tree(n+1);
for (int i=1; i<n; i++) {
scanf("%d%d", &parent, &son);
father_node[son] = parent;
tree[parent].push_back(son);
}
build_node(1, tree);
scanf("%d", &m);
while (m--) {
char cmd[2];
int now_p = 0;
scanf("%s%d", cmd, &n);
if (cmd[0]== 'C') {
delta = has_apple[n] > 0? -1: 1; // 三元操作符要比 delta = deltas[ has_apple[n]] 快,就这个一个操作减少数百(数十?)毫秒(总共也就跑了 1.2 s)
has_apple[n] = (has_apple[n] + 1)%2;
now_p = n;
sum_node[n] += delta;
while (true) { // 这个更新操作很耗费时间,同样的更新操作。它要比树状数组多两个量级(真是看不懂)
if (father_node[now_p] == -1)
break;
sum_node[father_node[now_p]] += delta;
now_p = father_node[now_p];
#ifdef DEBUG
op++;
#endif
}
}
else {
;
#ifndef DEBUG
ans.push_back(sum_node[n]);
#endif
}
}
#ifdef DEBUG
printf("%lld\n", op);
#else
for (int i=0; i< ans.size(); i++)
printf("%lld\n", ans[i]);
#endif
}
我提交的程序比 hzw 程序在时间上差不多,但是内存用了 8000 k。 不知道线段树能不能过这题?我的代码还是不够优美,网友 bigbigship 的代码很优美!
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e5+10; //这优雅
int has[maxn];
int n;
struct Tree{
struct nod{
int to,next;
}edge[maxn];
int in[maxn],out[maxn],tt;
int head[maxn],ip;
void init(){
tt=0,ip=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v){
edge[ip].to= v;
edge[ip].next = head[u];
head[u]= ip++;
}
void dfs(int u){
in[u]=++tt;
for(int i=head[u];i!=-1;i=edge[i].next){
int v = edge[i].to;
dfs(v);
}
out[u]=tt;
}
}G;
struct BIT { // 将 bit 变成结构体,优雅
int sum[maxn];
void init(){
memset(sum,0,sizeof(sum));
}
int lowbit(int x) {
return x&(-x);
}
void update(int pos,int x){
while(pos<=n){
sum[pos]+=x;
pos+=lowbit(pos);
}
}
int query(int pos){
int ans = 0;
while(pos>0){
ans+=sum[pos];
pos-=lowbit(pos);
}
return ans;
}
}T;
int main() {
while(~scanf("%d",&n)){
G.init();
T.init();
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
G.add(u,v);
}
G.dfs(1);
for(int i=1;i<=n;i++){
T.update(G.in[i],1);
has[i]=1;
}
int x,m;
scanf("%d",&m);
char s[2];
while(m--){
scanf("%s%d",s,&x);
if(s[0]=='C'){
if(has[x])
T.update(G.in[x],-1);
else
T.update(G.in[x],1);
has[x]^=1;
}
else
printf("%d\n",T.query(G.out[x])-T.query(G.in[x]-1));
}
}
return 0;
}
// 代码很优美
这题用线段树应该不好做,因为线段树是 2 叉平衡树!题目上没有保证建立的树是二叉平衡树!