bzoj刷题记录4.15-4.16
bzoj2588: Spoj 10628. Count on a tree
很漂亮的解法。
离散化之后在树上建立主席树,然后二分答案
k
,判断两点间小于等于
之后发现二分答案所在区间和主席树上区间是重合的,因此可以一起二分下去,复杂度变成了
O(nlgn)
。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long a = 0;
int c;
do c = getchar(); while(!isdigit(c));
while (isdigit(c)) {
a = a*10+c-'0';
c = getchar();
}
return a;
}
const int MAXN = 101000, lgn = 18;
int lc[MAXN*3*lgn], rc[MAXN*3*lgn], sum[MAXN*3*lgn], l[MAXN*3*lgn], r[MAXN*3*lgn];
int root[MAXN], depth[MAXN], rk[MAXN], n, m, dx[MAXN];
int top = 0;
struct node {
int to, next;
} edge[MAXN*2];
int head[MAXN], tp = 0;
void push(int i, int j)
{ ++tp, edge[tp] = (node){j, head[i]}, head[i] = tp; }
inline int new_node(int opl, int opr)
{ return ++top, l[top] = opl, r[top] = opr, lc[top]=rc[top]=sum[top] = 0, top;}
void build_tree(int &nd, int opl, int opr)
{
nd = new_node(opl, opr);
if (opl < opr)
build_tree(lc[nd], opl, (opl+opr)/2), build_tree(rc[nd], (opl+opr)/2+1, opr);
}
void insert(int pre, int &nd, int pos)
{
if (l[pre] == r[pre]) nd = new_node(pos, pos), sum[nd] = sum[pre]+1; // 记得++++++++++
else {
nd = new_node(l[pre], r[pre]);
int mid = (l[pre] + r[pre])/2;
if (pos <= mid) insert(lc[pre], lc[nd], pos), rc[nd] = rc[pre];
else insert(rc[pre], rc[nd], pos), lc[nd] = lc[pre];
sum[nd] = sum[lc[nd]] + sum[rc[nd]];
}
}
int query(int nd, int opl, int opr)
{
if (opl > opr || !nd) return 0;
if (opl == l[nd] && opr == r[nd]) return sum[nd];
int mid = (l[nd]+r[nd])/2;
if (opl > mid) return query(rc[nd], opl, opr);
else if (opr <= mid) return query(lc[nd], opl, opr);
return query(lc[nd], opl, mid)+query(rc[nd], mid+1, opr);
}
int fa[MAXN][lgn];
void dfs(int nd, int f) // remember to insert 1 first
{
fa[nd][0] = f;
for (int i = head[nd]; i; i = edge[i].next) {
int to = edge[i].to;
if (f == to) continue;
depth[to] = depth[nd]+1;
insert(root[nd], root[to], rk[to]);
dfs(to, nd);
}
}
int lca(int a, int b)
{
if (depth[a] < depth[b]) swap(a, b);
for (int i = 0, dt = depth[a]-depth[b]; i < lgn; i++)
if ((1<<i)&dt) a = fa[a][i];
if (a == b) return a;
for (int i = lgn-1; i >= 0; i--)
if (fa[a][i] != fa[b][i])
a = fa[a][i], b = fa[b][i];
return fa[a][0];
}
void dx_init()
{
memcpy(dx, rk, sizeof rk);
sort(dx+1, dx+n+1);
}
int dx_num(int num)
{
int l = 1, r = n, mid;
while (l <= r) {
mid = (l+r)>>1;
if (dx[mid] < num) l = mid+1;
else r = mid-1;
}
return l;
}
void init()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) rk[i] = read();
dx_init();
for (int i = 1; i <= n; i++) rk[i] = dx_num(rk[i]);
for (int i = 1; i < n; i++) {
int u, v; u = read(), v = read();
push(u, v), push(v, u);
}
depth[1] = 0; build_tree(root[0], 1, n);
insert(root[0], root[1], rk[1]);
dfs(1, 0);
for (int j = 1; j < lgn; j++)
for (int i = 1; i <= n; i++)
fa[i][j] = fa[fa[i][j-1]][j-1];
}
int ask(int i, int j, int k)
{
int l = 1, r = n;
int lp = lca(i, j), g = fa[lp][0];
int a = root[i], b = root[j], c = root[lp], d = root[g];
while (l < r) {
int mid = (l+r)/2;
int tmp = sum[lc[a]]+sum[lc[b]]-sum[lc[c]]-sum[lc[d]];
if (tmp < k) k -= tmp, l = mid+1, a = rc[a], b = rc[b], c = rc[c], d = rc[d];
else r = mid, a = lc[a], b = lc[b], c = lc[c], d = lc[d];
}
return l;
}
int main()
{
init();
int u, v, k, lastans = 0;
for (int i = 1; i <= m; i++) {
u = read(), v = read(), k = read(); u ^= lastans;
printf("%d", lastans = dx[ask(u, v, k)]);
if(i!=m) printf("\n");
}
return 0;
}
bzoj1066: [SCOI2007]蜥蜴
显然最大流建图…
愉悦身心。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005, MAXM = MAXN*100, inf = 12345678;
struct node {
int to, flow, next, neg;
} edge[MAXM];
int head[MAXN], top = 0;
inline void push(int i, int j, int f)
{
++top, edge[top] = (node) {j, f, head[i], top+1}, head[i] = top;
++top, edge[top] = (node) {i, 0, head[j], top-1}, head[j] = top;
}
int vis[MAXN], bfstime = 0, lev[MAXN];
queue<int> que;
int S = 1001, T = 1002;
bool bfs()
{
for (que.push(S), vis[S] = ++bfstime, lev[S] = 1; !que.empty(); que.pop()) {
int f = que.front(), to, flow;
for (int i = head[f]; i; i = edge[i].next) {
if (to = edge[i].to, flow = edge[i].flow, vis[to] == bfstime || !flow)
continue;
vis[to] = bfstime, lev[to] = lev[f]+1, que.push(to);
}
}
return vis[T] == bfstime;
}
int dfs(int nd, int fl = inf)
{
if (nd == T || !fl) return fl;
int ans = 0, t;
for (int i = head[nd]; i; i = edge[i].next) {
int to = edge[i].to, flow = edge[i].flow;
if (lev[to] != lev[nd]+1 || !flow) continue;
t = dfs(to, min(fl, flow));
ans += t, edge[i].flow -= t;
fl -= t, edge[edge[i].neg].flow += t;
}
if (fl) lev[nd] = -1;
return ans;
}
int dinic()
{
int ans = 0;
while (bfs()) ans += dfs(S);
return ans;
}
int r, c, d;
inline int number(int i, int j, int tp)
{
return (i-1)*c+j+tp*r*c;
}
char str[40];
int main()
{
scanf("%d%d%d", &r, &c, &d);
for (int i = 1; i <= r; i++) {
scanf("%s", str+1);
for (int j = 1; j <= c; j++) {
push(number(i, j, 0), number(i, j, 1), str[j]-'0');
if (i <= d || j <= d || r-i+1 <= d || c-j+1 <= d)
push(number(i, j, 1), T, inf);
}
}
for (int i = 1; i <= r; i++)
for (int j = 1; j <= c; j++)
for (int k = 1; k <= r; k++)
for (int l = 1; l <= c; l++)
if (!(i==k && j==l) && abs(i-k)+abs(j-l) <= d)
push(number(i, j, 1), number(k, l, 0), inf);
int cnt = 0;
for (int i = 1; i <= r; i++) {
scanf("%s", str+1);
for (int j = 1; j <= c; j++)
if (str[j] == 'L')
push(S, number(i, j, 0), 1), cnt++;
}
cout << cnt-dinic() << endl;
return 0;
}
bzoj3673/3674: 可持久化并查集
学习了一发rope
大法。一个trick是在路径压缩时如果没有修改就不要replace
了,否则会mle.
3673
#include <bits/stdc++.h>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
const int MAXN = 2e4+5;
rope<int> *fa[MAXN];
int findfa(int i, int nd)
{
if (fa[i]->at(nd)) {
int p = findfa(i, fa[i]->at(nd));
fa[i]->replace(nd, p);
return p;
}
return nd;
}
void link(int T, int i, int j)
{
int a = findfa(T, i), b = findfa(T, j);
if (a != b) fa[T]->replace(a, b);
}
bool linked(int T, int i, int j)
{ return findfa(T, i) == findfa(T, j); }
int n, m;
int a[MAXN];
int main()
{
scanf("%d%d", &n, &m);
memset(a, 0, sizeof a);
fa[0] = new rope<int>(a, a+n+1);
for (int i = 1; i <= m; i++) {
int tp; scanf("%d", &tp);
fa[i] = new rope<int>(*fa[i-1]);
if (tp == 1) {
int u, v; scanf("%d%d", &u, &v);
link(i, u, v);
} else if (tp == 2) {
int k; scanf("%d", &k);
if (k >= i) continue;
fa[i] = new rope<int>(*fa[k]);
} else {
int u, v; scanf("%d%d", &u, &v);
printf("%d\n", linked(i, u, v));
}
}
return 0;
}
3674
#include <bits/stdc++.h>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
const int MAXN = 2e5+5;
rope<int> *fa[MAXN];
int findfa(int i, int nd)
{
if (fa[i]->at(nd)) {
int p = findfa(i, fa[i]->at(nd));
if (fa[i]->at(nd) != p) fa[i]->replace(nd, p);
return p;
}
return nd;
}
void link(int T, int i, int j)
{
int a = findfa(T, i), b = findfa(T, j);
if (a != b) fa[T]->replace(a, b);
}
bool linked(int T, int i, int j)
{ return findfa(T, i) == findfa(T, j); }
int n, m;
int a[MAXN];
int main()
{
scanf("%d%d", &n, &m);
memset(a, 0, sizeof a);
fa[0] = new rope<int>(a, a+n+1);
int lastans = 0;
for (int i = 1; i <= m; i++) {
int tp; scanf("%d", &tp);
fa[i] = new rope<int>(*fa[i-1]);
if (tp == 1) {
int u, v; scanf("%d%d", &u, &v); u ^= lastans, v ^= lastans;
link(i, u, v);
} else if (tp == 2) {
int k; scanf("%d", &k); k ^= lastans;
if (k >= i) continue;
fa[i] = new rope<int>(*fa[k]);
} else {
int u, v; scanf("%d%d", &u, &v); u ^= lastans, v ^= lastans;
printf("%d\n", linked(i, u, v)); lastans = linked(i, u, v);
}
}
return 0;
}
接下来是一波莫比乌斯反演练习
莫比乌斯函数定义为:
符号 p(k) 为k中蕴含素因子p的个数。
bzoj2440: [中山市选2011]完全平方数
包含平方数 ⟺ 其素数分解中某一项幂次大于等于2。容易想到和 μ 的关系。
根据容斥原理可以列出小于 x 的符合条件的数总数为:
前两个东西的乘积就是 μ 的定义,因此可以整理为:
由于 μ 是积性函数,即对于 gcd(i,j)=1,μ(ij)=μ(i)μ(j) ,可以用线性筛搞出来。之后只要二分答案就好了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int not_prime[MAXN], prime[MAXN], top = 0, mu[MAXN];
void get_prime(int n)
{
memset(not_prime, 0, sizeof not_prime);
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!not_prime[i]) prime[++top] = i, mu[i] = -1;
for (int j = 1; j <= top && prime[j]*i <= n; j++) {
not_prime[prime[j]*i] = 1;
if (i%prime[j] == 0) { mu[i*prime[j]] = 0; break; }
mu[i*prime[j]] = -mu[i];
}
}
}
long long ans(long long n)
{
long long tp = (long long)(sqrt(n)+0.05);
long long ans = 0;
for (long long i = 1; i <= tp; i++)
ans += mu[i]*(n/(i*i));
return ans;
}
int main()
{
get_prime(100000);
int T; scanf("%d", &T);
while (T--) {
long long n; scanf("%lld", &n);
long long l = 1, r = 1e10;
while (l <= r) {
long long mid = (l+r)/2;
if (ans(mid) < n) l = mid+1;
else r = mid-1;
}
cout << l << endl;
}
return 0;
}
bzoj2301: [HAOI2011]Problem b
基本上一下午就在肝这个题…感觉理解力捉急。
首先学习一个 μ 的基本性质:
以及莫比乌斯反演定理:
和另一种表现形式:
后一种更常用一些,因为处理 k=1,2,3… 是常见的。这两个定理的证明都依赖于上面的性质。
考虑所求的可以用如下式配合容斥原理完成:
设:
则有:
由于:
根据反演定理:
由于后面的除法后取整最多只有 O(n√) 种取值,可以将许多项连续处理。考虑等式的成立条件:
由于:
满足 0≤c<1 ,联立以上若干式,可得:
满足:
由于对称性可以知道:
由此可以知道,于
k
处取到的值知道
ans += (sum[last]-sum[i-1])*(p/i/k)*(q/i/k);
k = last+1;
就得到了 O(nn√) 处理 f(p,q,i) 的算法,再由容斥原理简单处理即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
int prime[MAXN], not_prime[MAXN], mu[MAXN], s[MAXN], top = 0, n;
void get_prime()
{
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!not_prime[i]) { prime[++top] = i, mu[i] = -1; }
for (int j = 1; j <= top && prime[j]*i <= n; j++) {
not_prime[prime[j]*i] = 1;
if (i%prime[j] == 0) {
mu[prime[j]*i] = 0;
break;
}
mu[prime[j]*i] = -mu[i];
}
}
}
void init()
{
n = 50000;
get_prime();
s[0] = 0;
for (int i = 1; i <= n; i++) s[i] = s[i-1] + mu[i];
}
int f(int x, int p, int q)
{
int ans = 0, last = 0;
if (p > q) swap(p, q);
p /= x, q /= x;
for (int i = 1; i <= p; i = last+1) {
last = min(p/(p/i), q/(q/i));
ans += (s[last]-s[i-1])*(p/i)*(q/i);
}
return ans;
}
int f2(int i, int p, int q)
{
int ans = 0;
for (int k = 1; (p/(k*i))*(q/(k*i)); k++)
ans += mu[k]*(p/(k*i))*(q/(k*i));
return ans;
}
int main()
{
int a, b, c, d, k;
init();
int n; scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
int ans;
printf("%d\n", ans = f(k, b, d)-f(k, a-1, d)-f(k, b, c-1)+f(k, a-1, c-1));
}
return 0;
}
ps:如果能不构造 F <script type="math/tex" id="MathJax-Element-39">F</script>直接用处理和式解决问题岂不是很棒棒吗?