【比赛】201709洛谷月赛

传送门

https://www.luogu.org/contest/show?tid=3420

T1 预生成密码

题意

    给出a,b,c三个数的与(&)x或(|)y和(sum)z,求a,b,c. 希望a尽可能小,a相同则b尽可能小,b相同则c尽可能小.

思路

    a = x, b = z - x - y, c = y
    将x,y,z用二进制表示,先考虑x,y. 若x的第i位上为1,则a,b,c的第i位上均为1,若y的第i位为0,则a,b,c的第i位上为0. 否则a,b,c三个数,第i位上上总有1个或2个数为1. 为使a最小,则将这1个或2个1全部放在b,c上,a = x. 总有一个1在c上,c = y. 于是b = z - x - y.

代码

#include
using namespace std;
long long a,b,c;
int main() {
    while(scanf("%lld%lld%lld",&a,&b,&c)!=EOF)
        printf("%lld %lld %lld\n",a,c-a-b,b);
    return 0;
}

T2 康娜的线段树

题意

    对长度为n的序列建立线段树维护区间和。进行若干次区间加,每次暴力修改到底后,从根节点开始每次等概率地选择一个子节点进入,直到进入叶子节点。将一路经过的叶子节点权值和累加,求权值和的期望值。

思路

    期望值 = val[i](1/(2d[i]) ,val[i]表示点线段树中的点i保存的区间和,d[i]为点i在线段树中的深度.
    考虑每次对单点i加add,实质为 ans+=add1/(2d[j]) ,j为点i在线段树中的节点到根节点路径上的所有点。于是对于点1~n,处理出 a[i]=1/(2d[j]) . 而每次修改的是区间,那么对a[i]维护一个前缀和数组sum[i]即可修改。
    在树上问题中涉及每次都从当前点到根节点的操作时,可以考虑前缀和思想解题。
    亲测long double 保存a[] TLE 3个点,于是在保存时乘seed = 2^(ceil(log(n)/log(2)),开long long.最后/seed即可,避免爆long long,先约去seed与qwq的公约数。

代码

#include
#include
using namespace std;

typedef long long LL;

const int sm = 2e6 + 5;

int n,m,u;

LL g,sd,qwq,ans;

LL a[sm],sum[sm],val[sm];

int Max(int x,int y) { return x>y?x:y; }

template <typename T> void read(T &x) {
    char ch = getchar(); x = 0; int f = 1;
    while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') x = x*10 + ch - '0', ch=getchar();
    x *= f; 
}

void Build(int rt,int l,int r,int d) {
    val[rt] = (1<<(sd-d)) + val[rt>>1];
    if(l==r) {
        read(u);
        a[l] = val[rt];
        ans += 1ll * a[l] * u;
        return;
    }

    int m = (l + r) >> 1;

    Build(rt<<1,l,m,d+1);
    Build(rt<<1|1,m+1,r,d+1);
}

LL gcd(LL x,LL y) { return y==0?x:gcd(y,x%y); }

int main() {

    int u,v,w;

    read(n),read(m),read(qwq);

    sd = ceil(log2(n));

    Build(1,1,n,0);

    sd = 1ll << sd;

    g = gcd(sd,qwq);
    sd/=g,qwq/=g;

    for(int i = 1; i <= n; ++i)
        sum[i] = sum[i-1] + a[i];

    for(int i = 1; i <=m; ++i) {
        read(u),read(v),read(w);
        ans += 1ll * (sum[v] - sum[u-1]) * w;
     `
  printf("%lld\n",ans*qwq/sd);
    }

    return 0;
}

T3 aaa被续

题意

    给出一颗以1为根n个节点的树并定义 sum ,对每个节点k按照如下方法计算 subsum ,将k以及它的子树节点按照权值从大到小v排序后得到一个权值序列 val1,val2......vali subsum[k]=val1i+val2(i1)+...vali1 ,求 sum=ni=1subsum[i]

思路

    考虑每个点的权值对于最终答案的贡献次数。
    节点i的权值被贡献,一种可能是计算 subsum[i] ,另一种可能是计算i的祖先节点g的 subsum[g] 时,在g的子树中存在点 j,valj<vali . 因此将点按照val从小到大排序,依次插入。插入点i即将点i到点1的路径上所有点的计数器+1,统计点i到点1的路径上所有点的计数器的和 S ,S即为点i的权值对于最终答案的贡献次数。
    树链剖分+线段树实现。

代码

#include
#include
#include
#define f first
#define s second
using namespace std;

typedef long long LL;

const int sm = 5e5 + 10;
const int Mod = 1e9+7;

LL ret;
int n,tot;

int to[sm<<1],nxt[sm<<1],hd[sm];

LL cnt[sm<<2],mk[sm<<2];
int dp[sm],fa[sm],sn[sm],sz[sm],top[sm],Bn[sm];

pair<int,int> p[sm];

void Swap(int &x,int &y) { int t = x; x = y; y = t; } 

void read(int &x) {
    char ch = getchar(); x = 0; 
    while(ch > '9' || ch < '0') ch = getchar();
    while(ch>='0'&&ch<='9') x = x*10 + ch - '0', ch = getchar();
}

void Add(int u,int v) {
    to[++tot] = v, nxt[tot] = hd[u], hd[u] = tot;
    to[++tot] = u, nxt[tot] = hd[v], hd[v] = tot;
}

void Dfsa(int x,int ff) {
    fa[x] = ff, dp[x] = dp[ff]+1, sz[x] = 1;

    for(int i = hd[x];i;i = nxt[i])
        if(to[i]!=ff) {
            Dfsa(to[i],x);
            sz[x]+=sz[to[i]];
            if(sz[to[i]] > sz[sn[x]])
                sn[x] = to[i];
        }
}

void Dfsb(int x,int Top) {
    Bn[x] = ++tot, top[x] = Top;

    if(sn[x]) Dfsb(sn[x],Top);

    for(int i = hd[x]; i; i = nxt[i])
        if(to[i]!=fa[x]&&to[i]!=sn[x])
            Dfsb(to[i],to[i]);
}

void Pd(int rt,int l,int m,int r) {
    cnt[rt<<1] = (cnt[rt<<1] + (m-l+1)*mk[rt]) % Mod;
    cnt[rt<<1|1] = (cnt[rt<<1|1] + (r-m)*mk[rt]) % Mod;
    mk[rt<<1] = (mk[rt<<1] + mk[rt]) % Mod;    
    mk[rt<<1|1] = (mk[rt<<1|1] + mk[rt]) % Mod;
    mk[rt] = 0;
}

void Ins(int rt,int l,int r,int a,int b) {
    if(a<=l&&r<=b) {
        mk[rt] = (mk[rt] + 1) % Mod;
        cnt[rt] = (cnt[rt] + (r-l+1)) % Mod;
        return;
    }
    int m = (l + r) >> 1;
    if(mk[rt]) Pd(rt,l,m,r);
    if(a <= m) Ins(rt<<1,l,m,a,b);
    if(b >  m) Ins(rt<<1|1,m+1,r,a,b);
    cnt[rt] = cnt[rt<<1] + cnt[rt<<1|1];
}

LL Query(int rt,int l,int r,int a,int b) {
    if(a<=l&&r<=b) return cnt[rt];
    int m = (l + r) >> 1; LL ans = 0;
    if(mk[rt]) Pd(rt,l,m,r);
    if(a <= m) ans = (ans + Query(rt<<1,l,m,a,b)) % Mod;
    if(b >  m) ans = (ans + Query(rt<<1|1,m+1,r,a,b)) % Mod;
    cnt[rt] = cnt[rt<<1] + cnt[rt<<1|1];
    return ans;
}

LL Calc(int x,int y) {
    LL sum = 0;
    while(top[x]!=top[y]) {
        if(dp[x] < dp[y]) Swap(x,y);
        Ins(1,1,n,Bn[top[x]],Bn[x]);
        sum = (sum + Query(1,1,n,Bn[top[x]],Bn[x])) % Mod;
        x = fa[top[x]];
    }
    if(dp[x] < dp[y]) Swap(x,y);
    Ins(1,1,n,Bn[y],Bn[x]);
    sum = (sum + Query(1,1,n,Bn[y],Bn[x])) % Mod;
    return sum;
}

int main() {
    int u,v;

    read(n);
    for(int i = 1; i < n; ++i)
        read(u),read(v),Add(u,v);

    for(int i = 1; i <= n; ++i)
        read(p[i].f),p[i].s = i;

    sort(p+1,p+n+1);

    Dfsa(1,0), tot = 0, Dfsb(1,1);

    for(int i = 1; i <= n; ++i)
        ret = (ret + 1ll*Calc(1,p[i].s)*p[i].f) % Mod;    

    printf("%lld\n",ret);

    return 0;
}

你可能感兴趣的:(线段树,树链剖分,比赛)