BZOJ 2017省队十连测推广赛1

A 4765 : 分块
B 4766 : 矩阵树定理或找规律
C 4767 : 容斥+DP

感觉难度应该是A>B>C?
可惜周二晚上在训练,不然也许就能全A了……


A 4765 普通计算姬

分块分块再分块……
想了一想感觉没什么树上的算法能用,考虑分块乱搞。把操作分根号块,每次进入新块时重建一整棵树。那么一次询问至多只会对应根号个修改。对于每一个点x,记录从x到根的路径上经过了1~n的哪些点,这同样也可以分块,对于根号个修改点直接查贡献即可。因为分块了,查完之后还会有一些块外小点没统计到。因小点不超过根号个,对于这些小点就暴力查子树和,这需要一种修改 O(n) 询问 O(1) 的数据结构,同样还是可以分块……总时间复杂度 O(nn)

#include<cstdio>
#include<cstring>
#define S 900
#define N 200005
#define ll unsigned long long
#define R register
using namespace std;
namespace runzhe2000
{
    inline int read()
    {
        R int r = 0; R char c = getchar();
        for(; c < '0' || c > '9'; c = getchar());
        for(; c >='0' && c <='9'; r = r*10+c-'0', c = getchar());
        return r;
    }
    ll sum[N], big[N], small[N];
    int n, m, ecnt, d[N], last[N], root, beg[N], end[N], timer, fa[N], cnt[N][N/S+10], q[N], qv[S+10], qtop, bel[N], rebeg[N];
    struct edge{int next, to;}e[N<<1];
    void addedge(int a, int b)
    {
        e[++ecnt] = (edge){last[a], b};
        last[a] = ecnt;
    }
    struct block
    {
        ll big[N/S+10],small[N];
        block(){memset(big,0,sizeof big); memset(small,0,sizeof small);}
        void init()
        {
            memset(big,0,N<<3);
            memset(small,0,N<<3);
        }
        void add(int x, int v)
        {
            for(int i = bel[x], ii = N/S+10; i < ii; i++)
                big[i] += v;
            for(int i = x, ii = S*bel[x]; i <= ii; i++)
                small[i] += v; 
        }
        ll query(int x)
        {
            if(!x) return 0;
            return big[bel[x]-1] + small[x];
        }
    }blo;
    void init_sum(int x)
    {
        sum[x] = d[x];
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y == fa[x]) continue;
            init_sum(y);
            sum[x] += sum[y];
        }
    }
    void dfs(int x)
    {
        rebeg[beg[x] = ++timer] = x;
        memcpy(cnt[x], cnt[fa[x]], (N/S+10)<<2);
        for(int i = bel[x], ii = N/S+10; i < ii; i++) cnt[x][i]++;
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y == fa[x]) continue;
            fa[y] = x; dfs(y);
        }
        end[x] = timer;
    }
    ll calc(int lim)
    {
        if(!lim) return 0;
        R ll ret = sum[lim];
        for(R int i = 1; i <= qtop; i++)
            ret += (ll)cnt[q[i]][bel[lim]-1] * qv[i];
        for(int i = S*(bel[lim]-1)+1; i <= lim; i++)
            ret += blo.query(end[i]) - blo.query(beg[i]-1);
        return ret;
    }
    void main()
    {
        n = read(); m = read();
        for(int i = 1; i <= n; i++)
        {
            d[i] = read();
            bel[i] = bel[i-1] + (i%S==1?1:0);
        }
        for(int i = 1, a, b; i <= n; i++)
        {
            a = read(); b = read();
            if(!a) {root = b; continue;}
            if(!b) {root = a; continue;}
            addedge(a, b);
            addedge(b, a);
        }
        dfs(root);
        blo.init();
        for(R int i = 1, op, u, v; i <= m; i++)
        {
            op = read(), u = read(), v = read();
            if(i % S == 1)
            {
                blo.init(); 
                memset(sum,0,N<<3);
                init_sum(root);
                for(R int i = 1; i <= n; i++) sum[i] += sum[i-1];
                qtop = 0;
            }
            if(op == 1)
            { 
                q[++qtop] = u; qv[qtop] = v - d[u];
                blo.add(beg[u], v - d[u]);
                d[u] = v;
            }
            else
                printf("%llu\n",calc(v) - calc(u-1));
        }
    }
}
int main()
{
    runzhe2000::main();
}
B 4766 文艺计算姬

考场上打表找出规律,答案为 nm1mn1 。然后CJK学长教我用矩阵树定理直接推出来,orz orz orz。这里画矩阵不方便,就不写过程了。

#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
namespace runzhe2000
{
    ll n, m, p;
    ll mul(ll a, ll b)
    {
        ll r = 0;
        for(; a; a >>= 1)
        {
            if(a & 1) r = (r + b) % p;
            b = (b + b) % p;
        }
        return r;
    }
    ll fpow(ll a, ll b)
    {
        ll r = 1;
        for(; b; b >>= 1)
        {
            if(b&1) r = mul(r, a);
            a = mul(a, a);
        }
        return r;
    }
    void main()
    {
        scanf("%lld%lld%lld",&n,&m,&p);
        printf("%lld\n",mul(fpow(n,m-1),fpow(m,n-1)));
    }
}
int main()
{
    runzhe2000::main();
}
C 4767 两双手

把基底变成(0,1)(1,0),点坐标就好多了。显然容斥,记f[i][0/1]表示此时在i,已经至少经过的点数为偶数/奇数个时的方案数。由于图是拓扑图,可以直接DP出来。

#include<map>
#include<cstdio>
#include<algorithm>
#define mkp(u,v) make_pair(u,v) 
#define N 505
#define M 666666
#define MOD 1000000007
using namespace std;
namespace runzhe2000
{
    typedef long long ll;
    const double eps = 1e-3;
    struct point{int x, y;}p[N];
    int Ex, Ey, Ax, Ay, Bx, By, n, ecnt, pcnt, f[N][2], fac[M], inv[M], inc[M], deg[N], q[N], last[N];
    struct edge{int next, to;}e[N*N];
    map<pair<int,int>,bool> vis;
    void addedge(int a, int b)
    {
        e[++ecnt] = (edge){last[a], b};
        last[a] = ecnt;
    }
    bool addpoint(int x, int y)
    {
        int nx = By ? 
            ((double)y * Bx / By - x) / ((double)Ay * Bx / By - Ax) + eps:
            ((double)x * By / Bx - y) / ((double)Ax * By / Bx - Ay) + eps;
        int ny = Ax ? 
            ((double)x * Ay / Ax - y) / ((double)Bx * Ay / Ax - By) + eps:
            ((double)y * Ax / Ay - x) / ((double)By * Ax / Ay - Bx) + eps;
        if(x != nx * Ax + ny * Bx || y != nx * Ay + ny * By || nx < 0 || ny < 0 || vis[mkp(nx,ny)]) return false;
        vis[mkp(nx,ny)] = 1;
        p[++pcnt] = (point){nx, ny}; return true;
    }
    int C(int a, int b){return (ll)fac[a] * inc[b] % MOD * inc[a-b] % MOD;}
    void main()
    {
        fac[0] = fac[1] = inv[1] = inc[1] = inc[0] = 1;
        for(int i = 2; i < 666666; i++)
        {   
            fac[i] = (ll)fac[i-1] * i % MOD;
            inv[i] = (ll)(MOD - MOD/i) * inv[MOD % i] % MOD;
            inc[i] = (ll)inc[i-1] * inv[i] % MOD;
        }
        scanf("%d%d%d%d%d%d%d",&Ex,&Ey,&n,&Ax,&Ay,&Bx,&By);
        addpoint(0,0);
        for(int i = 1, x, y; i <= n; i++)
        {
            scanf("%d%d",&x,&y);
            if(x == 0 && y == 0){puts("0"); return;}
            if(x == Ex && y == Ey){puts("0"); return;}
            addpoint(x,y);
        }
        if(Ex == 0 && Ey == 0) {puts("1"); return;}
        if(!addpoint(Ex,Ey)) {puts("0"); return;}
        for(int i = 1; i <= pcnt; i++)   for(int j = 1; j <= pcnt; j++) if(i != j && p[i].x <= p[j].x && p[i].y <= p[j].y) addedge(i, j), deg[j]++;
        q[0] = 1; f[1][1] = 1;
        for(int head=0, tail=1; head<tail; head++)
        {
            int x = q[head];
            for(int i = last[x]; i; i = e[i].next)
            {
                int y = e[i].to, P = C(p[y].y - p[x].y + p[y].x - p[x].x, p[y].x - p[x].x);
                (f[y][0] += (ll)f[x][1] * P % MOD) %= MOD; 
                (f[y][1] += (ll)f[x][0] * P % MOD) %= MOD; 
                if(!(--deg[y])) q[tail++] = y;
            }
        }
        printf("%d\n",((f[pcnt][0] - f[pcnt][1]) % MOD + MOD) % MOD);
    }
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值