bzoj刷题记录4.15-4.16

bzoj刷题记录4.15-4.16


bzoj2588: Spoj 10628. Count on a tree

很漂亮的解法。
离散化之后在树上建立主席树,然后二分答案 k ,判断两点间小于等于k的有多少个元素,得到 O(nlg2n) 算法,在bzoj上会TLE。
之后发现二分答案所在区间和主席树上区间是重合的,因此可以一起二分下去,复杂度变成了 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;
}

接下来是一波莫比乌斯反演练习

莫比乌斯函数定义为:

μ(d)=[p,p(i)1](1)p(i)

符号 p(k) 为k中蕴含素因子p的个数。

bzoj2440: [中山市选2011]完全平方数

包含平方数 其素数分解中某一项幂次大于等于2。容易想到和 μ 的关系。

根据容斥原理可以列出小于 x 的符合条件的数总数为:

i[p,p(i)1](1)p(i)xi2

前两个东西的乘积就是 μ 的定义,因此可以整理为:

iμ(i)xi2

由于 μ 是积性函数,即对于 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

基本上一下午就在肝这个题…感觉理解力捉急。

首先学习一个 μ 的基本性质:

d|nμ(d)=[n=1]

以及莫比乌斯反演定理:

f(n)=d|nF(d)F(n)=d|nμ(nd)f(d)

和另一种表现形式:

F(i)=k1f(ki)f(i)=k1μ(k)F(ki)

后一种更常用一些,因为处理 k=1,2,3 是常见的。这两个定理的证明都依赖于上面的性质。

考虑所求的可以用如下式配合容斥原理完成:

f(i)=1xp1yq[(x,y)=i]

设:

F(i)=x,y[i|(x,y)]

则有:

F(i)=x,y[i|x][i|y]=xp[i|x]yq[i|y]=xp[i|x]itq[i|it]=x[i|x]qi=piqi

由于:

F(i)=k1x,y[ki=(x,y)]=k1f(ki)

根据反演定理:

f(i)=k1μ(k)F(ik)=k1μ(k)pikqik

由于后面的除法后取整最多只有 O(n) 种取值,可以将许多项连续处理。考虑等式的成立条件:

pik=pik

由于:

pik=pik+c1pik=pik+c2

满足 0c<1 ,联立以上若干式,可得:

0pikpik<1

满足:

kpi/pik(1)

由于对称性可以知道:

kqi/qik(2)

由此可以知道,于 k 处取到的值知道(1)(2)同时满足,两个底的乘积不变,记最大的 k last 。于是用分配律提出之,和式内的 μ 可以通过前缀和预处理之,则可以:

    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>直接用处理和式解决问题岂不是很棒棒吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值