[比赛][2017省队十连测推广赛1]

50 篇文章 0 订阅
3 篇文章 0 订阅

[比赛][2017省队十连测推广赛1]

Pre()

这是BZOJ上打的第一场比赛,也是博客里写的第一篇比赛题解。。。

T1

题目大意:

给定一棵 N<=100000 个节点的有根树,设 sum[i] 是以 i 为根的子树的权值和。命令1 u v是把节点 u 的权值改为v,命令 2 l r 是询问ri=lsum[i]

思路:

一开始的想法是把树分块,发现并不可做。然后考虑对 sum 序列分块分成 N 块,预处理 f[i][j] 为节点 j 的祖先中编号属于块i的数量,这个应该很好预处理的,显然在 dfs 的时候处理下面的式子(其中 u v的父亲):

f[i][v]=f[i][u]+(belong[v]==i)

这样子再预处理一个 s[i] 表示第 i 块的sum权值和,如果要把编号 u 的节点的权值从x改为 y 的话,那么对每个块的处理就是:

s[i]=s[i]+(yx)f[i][v]

这个式子显然成立(一个节点的权值改变对它和它的父亲都有贡献为 yx ,每个块的父亲数量之前已经预处理出来了)。

块和块整体的询问我们已经预处理好了,那么如果查询的时候除了块之间的还有整体块两旁不满一整块的一些点呢?

我们先处理一下 dfs 序把一棵树整理成序列,一个点及其子树在序列上肯定是连续的一段,单点修改的时候我们顺便把点 v dfs序上的位置在树状数组上也修改一下,然后统计的时候对那些零散的点暴力统计它的子树和(树状数组查询序列和)。

修改的时候每个块都要修改一遍,复杂度 O(N) ,查询的时候块最多有 O(N) 块,零散的点最多有 O(N) 个。预处理时每个点都要 O(N) 做一遍。

所以总复杂度是: O((N+M)N)

坑:

树状数组两个操作都打错了。。。贡献了一页的RE。

还有如果询问的 L R都在同一块内的话直接暴力做一遍就好了。

要开 unsigned long long

代码:
#include <bits/stdc++.h>
const int Maxn = 100110;
typedef unsigned long long ll;

using namespace std;
    inline char get(void) {
        static char buf[1000000], *p1 = buf, *p2 = buf;
        if (p1 == p2) {
            p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
            if (p1 == p2) return EOF;
        }
        return *p1++;
    }
    inline void read(int &x) {
        x = 0; static char c;
        for (; !(c >= '0' && c <= '9'); c = get());
        for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get());
    }
    inline void read(ll &x) {
        x = 0; static char c;
        for (; !(c >= '0' && c <= '9'); c = get());
        for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get());
    }
    inline void write(ll x) {
        if (!x) return (void)puts("0");
        if (x < 0) putchar('-'), x = -x;
        static short s[60], t;
        while (x) s[++t] = x % 10, x /= 10;
        while (t) putchar('0' + s[t--]);
        putchar('\n');
    }
int n, m;
ll t[Maxn];
inline void insert(int x, long long v) {
    for (; x < Maxn; x += (x & -x)) t[x] += v;
}
inline ll query(int x) {
    ll ret = 0;
    for (; x > 0; x -= (x & -x)) ret += t[x];
    return ret;
}
int head[Maxn], sub;
struct Edge {
    int to, nxt;
    Edge(void) {}
    Edge(const int &to, const int &nxt) : to(to), nxt(nxt) {}
} edge[Maxn << 1];
inline void add(const int &a, const int &b) {
    edge[++sub] = Edge(b, head[a]), head[a] = sub;
}
ll sum[320], a[Maxn];
int bb[320][Maxn], st[Maxn], ed[Maxn];
int block, belong[Maxn], root, dfn[Maxn], en[Maxn], ff;
inline void dfs(int u, int fa) {
    dfn[u] = ++ff;
    bb[belong[u]][u]++;
    for (int i = head[u], v; i; i = edge[i].nxt) {
        v = edge[i].to;
        if (fa == v) continue;
        for (int k = 1; k <= block; k++) 
            bb[k][v] = bb[k][u]; 
        dfs(v, u);
    }
    en[u] = ff;
}
int main(void) {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    read(n), read(m);
    for (int i = 1; i <= n; i++) read(a[i]);
    for (int i = 1; i <= n; i++) {
        int a, b;
        read(a), read(b);
        if (!a || !b) {
            root = a ^ b;
            continue;
        }
        add(a, b), add(b, a);
    }
    block = sqrt(n) + 1;
    for (int i = 1; i <= n; i++) {
        belong[i] = (i - 1) / block + 1;
        ed[belong[i]] = i;
        if (!st[belong[i]]) st[belong[i]] = i;
    }
    dfs(root, 0);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= block; j++) {
            sum[j] += bb[j][i] * a[i];
        }
        insert(dfn[i], a[i]);
    }

    int x, wa, al, bl;
    ll y, ret;
    //cout << "ok" << endl;
    while (m--) {       
        read(wa), read(x), read(y);
        if (wa == 2) {
            ret = 0;
            al = belong[x]; bl = belong[y];
            if (al == bl) {
                for (int i = x; i <= y; i++) ret += query(en[i]) - query(dfn[i] - 1);
                write(ret);
                continue;
            }
            for (int i = al + 1; i < bl; i++)
                ret += sum[i];
            for (int i = x; i <= ed[al]; i++) {
                ret += query(en[i]) - query(dfn[i] - 1);
            }
            for (int i = st[bl]; i <= y; i++) {
                ret += query(en[i]) - query(dfn[i] - 1);
            }
            write(ret);
        } else {
            for (int i = 1; i <= block; i++)
                sum[i] = sum[i] + (long long) bb[i][x] * (y - a[x]);
            insert(dfn[x], y - a[x]);
            a[x] = y;
        }
    }
    return 0;
}

T2

题目大意:

给定一个一边点数为 n ,另一边点数为m,共有 nm 条边的带标号完全二分图 Kn,m ,求其生成树个数。
答案要 %p

思路:

好神啊并不会,机房某位大神一搜发现是结论题。

答案就是: (nm1mn1)%p

于是快速幂套快速乘就水过了。。。

复杂度貌似是 O(log2N)

代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL fast_multi(LL m, LL n, LL mod)
{
    LL ans = 0;
    while (n)
    {
        if (n & 1)
            ans += m;
        m = (m + m) % mod;
        m %= mod;
        ans %= mod;
        n >>= 1;
    }
    return ans;
}
LL fast_pow(LL a, LL n, LL mod)
{
    LL ans = 1;
    while (n)
    {
        if (n & 1)
            ans = fast_multi(ans, a, mod);
        a = fast_multi(a, a, mod);
        ans %= mod;
        a %= mod;
        n >>= 1;
    }
    return ans;
}
LL a, b, c;
int main(void) {
    cin >> a >> b >> c;
    cout << fast_multi(fast_pow(a, b - 1, c), fast_pow(b, a - 1, c), c) << endl;
    return 0;
}

T3

题目大意:

给定两种移动方式 (u,v)>(u+Ax,v+Ay) (u,v)>(u+Bx,v+By) ,求从 (0,0) 移动到 (Ex,Ey) 共有多少种方法。

%1000000007

思路:

考虑把从 (0,0) 移动到 (ex,ey) 一个关于 (M,N) M 表示用第一种方法的数量,N表示用第二种方法的)的方程组:

{MAx+NBx=exMAy+NBy=ey

能从 (0,0) 移动到 (ex,ey) ,当且仅当 N M的解为正整数。

且对于确定的 (ex,ey) ,对应的 (M,N) 一定唯一(二元一次方程组的性质)。

这样子,我们可以把图重新建成一张坐标为 (M,N) 的网格图。

而从 (0,0) 移动到 (ex,ey) 的方案数就是 CMN+M

考虑 DP +容斥,对求出网格图上的所有点按 N 为第一关键字,M为第二关键字升序排列,过滤掉 M N大于最终点的点后,定义 f[i] 为走到点 i (排序后)的合法方案数。

状态转移方程就是:

f[i]=CMiMi+Nij=1i1f[j]CMiMjMiMj+NiNj(Mj>=Mi)

处理排列数的时候要预处理一下阶乘和逆元。。。当然逆元像我一样打了快速幂也可以。。。

代码:

#include <bits/stdc++.h>
const int Maxn = 510;
const int Mod = 1000000007;
typedef long long ll;
using namespace std;

inline char get(void) {
    static char buf[1000000], *p1 = buf, *p2 = buf;
    if (p1 == p2) {
        p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}
inline void read(ll &x) {
    x = 0; static char c; bool f = 0;
    for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') f = 1;
    for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (f) x = -x;
}
ll aX, aY, bX, bY, tot;
ll power(ll a, ll b) {
    ll ret = 1;
    while (b) {
        if (b & 1) (ret *= a) %= Mod;
        (a *= a) %= Mod;
        b >>= 1;
    }
    return ret;
}
ll ff[1000001];
ll fab(ll n) {
    return ff[n];
}
ll C(ll n, ll m) {
    ll a = fab(n + m), b = fab(m), c = fab(n);
    return a * power(b, Mod - 2) % Mod * power(c, Mod - 2) % Mod;
}
ll CC(ll n, ll m) {
    if (n < 0 || m < 0) return 0;
    else return C(n, m);
}
ll N, M, NN, MM;
inline void Clac(ll Ex, ll Ey) {
    ll t1 = (bY * aX - bX * aY), t2 = (Ey * aX - Ex * aY);
    if (t2 % t1 != 0) {
        N = -1;
        return;
    }
    N = t2 / t1;
    t1 = (bX * aY - bY * aX), t2 = (Ey * bX - Ex * bY);
    if (t2 % t1 != 0) {
        M = -1;
        return ;
    }
    M = t2 / t1;
    if (M < 0 || N < 0) return;
}
ll n, f[Maxn];
struct Pos {
    ll x, y;
    friend bool operator < (const Pos &a, const Pos &b) {
        if (a.x == b.x) return a.y < b.y; 
        return a.x < b.x;
    }
} pos[Maxn];
int main(void) {
    ll init = 1;
    ff[0] = 1;
    for (int i = 1; i <= 1000000; i++) {
        init = (init * i) % Mod;
        ff[i] = init;
    }
    ll Ex, Ey, tot = 0;
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    read(Ex), read(Ey), read(n);
    read(aX), read(aY), read(bX), read(bY);
    Clac(Ex, Ey); MM = M, NN = N;
    ll x, y;
    for (ll i = 1; i <= n; i++) {
        read(x), read(y);
        Clac(x, y);
        if (N < 0 || M < 0 || N > NN || M > MM) continue;
        tot++;
        pos[tot].x = N, pos[tot].y = M;
    }
    tot++;
    pos[tot].x = NN, pos[tot].y = MM;
    sort(pos + 1, pos + 1 + tot);

    for (ll i = 1; i <= tot; i++) {
        f[i] = CC(pos[i].x, pos[i].y);
        for (ll j = 1; j < i; j++)
            if (pos[j].y <= pos[i].y) 
                f[i] = (f[i] + Mod - (CC(pos[i].x - pos[j].x, pos[i].y - pos[j].y) * f[j]) % Mod) % Mod;
    }
    for (ll i = tot; i; i--) {
        if (f[i]) {
            cout << f[i] << endl;
            return 0;
        }
    }

    cout << 0 << endl;
    return 0;
}

撒花~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值