2017 Multi-University Training Contest 第一场

题解在:http://bestcoder.hdu.edu.cn/blog/2017-multi-university-training-contest-8-solutions-by-%E5%8C%97%E4%BA%AC%E8%88%AA%E7%A9%BA%E8%88%AA%E5%A4%A9%E5%A4%A7%E5%AD%A6/#comment-42

四题200多名吧
反正就只能切水题

1001
求2^m次方有多少位,一开始以为是找规律,但发现有误差,后来发现用公式变成log可以直接算出来。

1002
很坑,wa了很多次。但是思路一开始就是对了,算出各个字母的贡献值,然后排序,不过一开始没注意前导0

1006
置换群,然后xzn写完,帮他查了很多弱智错误。。然后过了

1011
找规律 我们可以发现是 (n 1 2 3 4 … ..n-2) + (n-1 1 2 3 ….n-2)的循环节。
然后敲了一发,就过了。

补题:
1003
题意:一棵树中每个节点都有一个颜色,求所有路径的sum。
每条路径值 = 这条路径经过的颜色种数。

这个问题的路径其实是可以分割的,比赛时一直以为不可以分割,所以不会。
既然可以分割,必然也不需要去枚举路径。

比如
1
\
2
\
3
这样一棵树,颜色1 经过他的路径条数有2条
颜色2 经过他的路径条数有3条
颜色3经过他的路径条数有2条 所以ans=7

1
\
2
/ \
1 3
这样一棵树,颜色1 经过他的路径条数有2+3条
颜色2 经过他的路径条数有3+3条
颜色3经过他的路径条数有2+1条 所以ans=14

我们可以再列举几个例子,仔细想想,这个推断竟然是成立的,惊了!
再仔细想想,其实是有道理的,因为每条路径上的每种颜色对答案的贡献值其实是1
所以对于每一种颜色来说,贡献值就是有多少条路径通过这一种颜色。
但是统计这个是很有可能重复统计的,所以我们反过来思考,有多少条路径没有经过某种颜色

然后我们跑出dfs序 每一种颜色(可能有多个点) 会把树 划分成许多块。
然后我们那总路径数,减去这些不经过这种颜色的路径数。

然后我们再看标程,哇,写的是真的好,这是世界上最难学的算法就是dfs。

typedef long long LL;
const int maxn = 200001;
int n, c[maxn], lnk[maxn], pos[maxn], ctr[maxn], rem[maxn];

//rem 存的是 当前节点所有子节点数 (当某颜色上面的节点没有当前颜色节点时,上面的所有节点块形成的每条路径不不经过这种颜色。


//ctr  存在的是下一个相同颜色节点 的 子节点个数。


LL ans;
struct Edge {
    int nxt, v;
} e[maxn << 1 | 1];
inline LL sum2(int x) {
    return (x * (x - 1LL)) >> 1;
}
int dfs(int u, int fa) {
    int su = 1, o = pos[c[u]];  // pos[c[u]] 当前颜色在dfs中上一个当前颜色的节点号
    pos[c[u]] = u; // 颜色x的位置更新为u,当前的
    for(int it = lnk[u]; it; it = e[it].nxt)  // 遍历
        if(e[it].v != fa) {
            ctr[u] = 0;        // ctr 要初始化为0,不然dfs与u相同的颜色时会累加到ctr[u]上来
            int sv = dfs(e[it].v, u);  // e[it].v这一块子树的节点数。不包括u
            ans -= sum2(sv - ctr[u]);  //(e[it].v这一块子树)所有子节点数-下一个相同颜色节点的子节点数= 他们之间的连通块的数目。
            //  还有一个忽略的地方: 如果ctr[u]=0,那么说明下面没有相同颜色,所以下面的块形成的路径也应该减去。
            su += sv;
        }
//    (o ? ctr[o] : rem[c[u]]) += su;
    /* 走到这一步时,dfs已经把u的所有的子树都扫过一遍了。
         如果o为真,代表前面最近出现c[u],节点号为o,且o 不可能是子树中出现的
        所以此时,当前节点 的所有子节点数(包括自己) 都加到 上一个节点o的ctr 中
    */
    if(o) ctr[o]+=su;    //
    else rem[c[u]]+=su;  //如果上面没出现过, 那么他所有的子树放进rem[当前颜色](把自己也放进去)
    pos[c[u]] = o;   // 还原当前颜色的节点号,所以每一个的o 都必然是上面最近的颜色x的位置。
    return su;   //返回包括自己+所有子树的节点数
}
int main() {
    //freopen("1.txt","r",stdin);
    for(int Case = 1; scanf("%d", &n) == 1; ++Case) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d", c + i);
            lnk[i] = rem[i] = 0;
        }
        for(int i = 1, u, v; i < n; ++i) {
            scanf("%d%d", &u, &v);
            e[i << 1] = (Edge){lnk[u], v};
            lnk[u] = i << 1;
            e[i << 1 | 1] = (Edge){lnk[v], u};
            lnk[v] = i << 1 | 1;    // 链式存图
        }
        ans = sum2(n) * n; // 一共有n(n-1)/2条路径,假设每条路径都经过n?
//        printf("%I64d\n",ans);
        dfs(1, -1);
//        printf("%I64d\n",ans);
        for(int i = 1; i <= n; ++i)  // 遍历所有节点
            ans -= sum2(n - rem[i]);
        printf("Case #%d: %I64d\n", Case, ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值