Task 14

T1

题目大意:

一棵特别大的二叉树上求LCA

简单分析:

求就求白, 没啥法

标算:

根据二叉树结点编号的性质

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
ll x, y; int n;
int dep(long long x) {return (int)(log(x)/log(2)); }
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld%lld", &x, &y);
        int ans = 0;
        if(x == y) { printf("0\n"); continue; }
        // x > y
        if(dep(x) < dep(y)) swap(x, y);
        while(dep(x) > dep(y)) { x /= 2; ans++; }
        if(x == y) { printf("%d\n", ans); continue; }
        while(x != y) { x /= 2; y /= 2; ans += 2; }
        printf("%d\n", ans);
    }
    return 0;
}

T2

题目大意:

你把n张画纸铺成一排,并将它们从1到n编号。你一共有c种颜色可用,这些颜色可以用0到c-1来编号。初始时,所有画纸的颜色都为1。你一共想进行k次作画,第i次作画时,你会等概率随机地选闭区间[Li,Ri]内的画纸的一个子集(可以为空),再随机挑一种颜色bi,并把挑出来的画纸都涂上该颜色。原有颜色a的画纸在涂上颜色b后,颜色会变成(a*b) mod c,这是这个世界的规律。
求出在k次作画结束后,每张画纸上的颜色对应的数字相加之和的期望

简单分析:

看起来是在算期望, 很高端的样子, 但是实质上是算概率, 然后将值按概率加权即可
当时抱着大概可以dp解的想法就写了
推导出的结论: 从一个集合中选取任意子集, 每个元素被选到的概率都是1/2

标算:

状态: col[i][j] 表示i点是j号颜色的概率是多少
转移: 枚举下一次转到那个颜色
注意: 颜色变化方式与reality不同

#include<cstdio>
#include<cstring>
const int N = 110;
double ans, col[N][N], tmp[N];
int n, c, k;
int main() {
    scanf("%d%d%d", &n, &c, &k);
    double sb = 1.0/c; //每种颜色的概率 
    for(int i = 1; i <= n; ++i) col[i][1] = 1.0; //所有点在最初都是1号颜色 
    for(int i = 1; i <= k; ++i) {
        int l, r;
        scanf("%d%d", &l, &r);
        for(int j = l; j <= r; ++j) { //区间内每一个点 
            memset(tmp, 0, sizeof(tmp));
            for(int a = 1; a < c; ++a) { //该点每一种颜色 
                col[j][a] = col[j][a]*(0.5);
                for(int b = 1; b < c; ++b) {
                    tmp[(a*b)%c] += col[j][a]*sb;
                }
            }
            for(int a = 1; a < c; ++a) { // 将修改与已知答案合并 
                col[j][a] += tmp[a];
            } 
        }
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j < c; ++j) {
            ans += col[i][j]*(double)(j); //由概率导出期望 
        }
    }
    printf("%.3lf\n", ans);
    return 0;
}

T3

不可做的题目大意:

收获香子兰(香子兰: qwq), 必须从家到花田收获, 再到花店卖掉, 再到花田播种, 再回家
限制: 前(n-2)/2个被收割的花田也是前(n-2)/2个被播种的

用命分析:

没命了qwq

咋都看不懂的标算:

状态压缩DP
Floyd预处理两两之间最短路,并预处理:
F[i][sta]表示从家开始,当前走到点i,已经走过sta中的点,走过的最短距离是多少
G[i][sta]表示从花店开始,当前走到点i,已经走过sta中的点,走过的最短距离是多少
枚举先收割哪些花田,记为A,其余的花田记为B
分交货前和交货后两段,单独计算最短距离。以交货前为例:
枚举A中最后一个收割的点i、B中第一个收割的点j
求min{F[i][A]+dis(i,j)+G[j][B]}

#include<cstdio>
#include<algorithm>
using namespace std;
const int inf = 1e9 + 7;
const int N = 24;
const int M = 1050000;
int a[N][N], d[N][N], f[2][N][M], e[N], cnt[M];
int n, m, ans;
void pre() { //预处理每个二进制数中有几个1 
    e[0] = 1;
    for(int i = 1; i <= 22; ++i) e[i] = e[i-1]<<1;
    for(int i = 0; i < e[20]; ++i) {
        for(int x = i; x != 0; x >>= 1) {
            cnt[i] += x&1;
        }
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
            d[i][j] = inf*(i!=j);
        }
    }
}
void Floyd() {
    for(int k = 1; k <= n; ++k) {
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= n; ++j) {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    pre();
    for(int i = 1; i <= m; ++i) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        ++x; ++y;
        if(z < d[x][y]) d[x][y] = d[y][x] = z;
    }
    Floyd();
    if(n == 3) {
        printf("%d\n", (d[1][2]+d[2][3])*2);
        return 0;
    }

    int n1 = (n-2)/2;
    int n2 = n-2 - n1;
//  求从家、花店开始, 走到点i, 经过的点为j的最短路
//  q = 0: 从家开始;  q = 1: 从花店开始 
    for(int q = 0; q <= 1; ++q) {
        //初始状态 
        for(int i = 1; i <= n; ++i) {
            for(int j = 0; j < e[n-2]; ++j) {
                f[q][i][j] = inf;
            }
        }
        if(q == 0) {
            for(int i = 2; i < n; ++i) {
                f[q][i][e[i-2]] = d[1][i];
            }
        } else if(q == 1) {
            for(int i = 2; i < n; ++i) {
                f[q][i][e[i-2]] = d[n][i];
            }
        }
        //dp 
        for(int j = 1; j < e[n-2]; ++j) {
            if(cnt[j] < n2) {
                for(int i = 2; i < n; ++i) {
                    if(f[q][i][j] < inf) {
                        for(int k = 2; k < n; ++k) {
                            f[q][k][j|e[k-2]] = min(f[q][k][j|e[k-2]], 
                                f[q][i][j]+d[i][k]);
                        }
                    }
                }
            }
        }
    }
    ans = inf;
    //枚举先走到的一半为sta 
    for(int sta = 0; sta < e[n-2]; ++sta) {
        if(cnt[sta] == n1) {
            //前半段 
            int x = inf; //x记录前半段的最短距离
            //枚举前一半中最后一个收割的点是i 
            for(int i = 2; i < n; ++i) {
                if(sta&e[i-2]) {
                    //枚举后一半中第一个收割的点是j 
                    for(int j = 2; j < n; ++j) {
                        if(!(sta&e[j-2])) {
                            x = min(x, 
                                f[0][i][sta]+d[i][j]+f[1][j][e[n-2]-1-sta]);
                        }
                    }
                }
            }
            //后半段
            //枚举前一半中最后一个播种的点是i 
            for(int i = 2; i < n; ++i) {
                if(sta&e[i-2]) {
                    //枚举后一半中第一个播种的点是j 
                    for(int j = 2; j < n; ++j) {
                        if(!(sta&e[j-2])) {
                            ans = min(ans, 
                                x+f[1][i][sta]+d[i][j]+f[0][j][e[n-2]-1-sta]);
                        }
                    }
                }
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值