【20190915】多校联考

T1

【题目描述】
Akari 的学校的校门前生长着一排 n 棵树,从西向东依次编号为 1 ∼ n。相邻两棵树间的距离 都是 1。 Akari 上课的教学楼恰好在树 1 旁,所以每个课间,Akari 都很想走出教室,上树活动。Akari 会依次经过 m 棵树,从树 1 一路向东跳到树 n。临近上课时,Akari 会再次上树,经过 m 棵树从树 n 一路向西跳到树 1 ,准备上课。由于 Akari 睡眠很充足,Akari 每次跳跃至少会移动 k 的距离, 因此 Akari 在上树前需要合理规划她的跳跃路线。我们称每次上树过程中 Akari 跳过的全部 m 棵 树(包含树 1 和树 n)的集合为一条树上路径。 Akari 喜欢按不同的顺序观察各种树木,因此她每次上树时选择的树上路径不会与之前选择过 的重复。这意味着,Akari 不会选择之前的课间选过的树上路径,且在从树 n 跳回树 1 时,也不会 沿这次跳到树 n 的树上路径原路返回。 如果一次课间开始时,Akari 找不到符合条件的树上路径,那么她从此会放弃上树活动,开始 专心学习。如果一次课间即将即将结束时,Akari 还在树 n 且找不到符合条件的树上路径回到树 1, 她就会十分沮丧,选择逃课。 请你帮助 Akari 判断,她是否会在某个课间选择逃课。

【题解】
显然原问题可以转化为树上路径的数目。
由于每一次需要至少往后跳k棵树,若把每一个落脚的树看做一个块的开头,则原问题可转化为将长度为n-1(第n棵树单独占一个块)的序列划分为m-1(同前)个块,且每个块的大小至少为k的方案数。
其实仔细想想,还可以进一步转化:将n-1个相同小球放入m-1个不同盒子里,每一个盒子里的球数不少于k。这就是一个十分经典的组合问题了。答案为 C n − 1 − ( m − 1 ) k + m − 1 − 1 m − 1 C_{n-1-(m-1)k+m-1-1}^{m-1} Cn1(m1)k+m11m1
这么简单的问题居然想了1.5h

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 2;
inline ll getint()
{
    ll ret = 0, flg = 1; char c;
    while((c = getchar()) < '0' || c > '9')
        if(c == '-') flg = -1;
    while(c >= '0' && c <= '9')
        ret = ret * 10 + c - '0', c = getchar();
    return ret * flg;
}
inline ll c(int a, int b)
{
    if(a < b) return 0;
    return 1;
}
inline ll C(ll a, ll b)
{
    if(a < b) return 0;
    if(a == b || b == 0) return 1;
    return C(a / mod, b / mod) * c(a % mod, b % mod) % mod;
}
int main()
{
    freopen("phantasm.in", "r", stdin);
    freopen("phantasm.out","w",stdout);
    int T;
    ll n, m, k;
    scanf("%d", &T);
    while(T--)
    {
        n = getint(), m = getint(), k = getint(), --n, --m;
        ll N = n - m * k, M = m;
        puts(C(N + M - 1, M - 1) ? "Yes" : "No");
    }
}
T2

【题目描述】
小 A 的城市里有 n 座工厂,编号分别为 1 ∼ n。工厂间连有 n − 1 条双向管道,形成一个无向 连通图,其中每条管道都有一定的长度,连接在两座不同的工厂间。 每座工厂都装有废水处理设施,工厂 i 的蓄水量记为 ci。由于工厂规模有限,工厂产生的废水 必须经由管道输送到另一座工厂进行处理。 工厂 u 将废水输送到工厂 v 处理时,所需的运输成本等于无向图中 u, v 间最短路径的长度,并 且会产生 cu − cv 的额外成本(可能为负)。总成本等于运输成本与额外成本的和。 为了降低污染,在接下来的 q 天内,每一天只有一座工厂会产生废水。你需要确定这座工厂将 废水输送到哪一座工厂进行处理,可使得总成本最小。由于选择可能不唯一,你只需输出最小的总 成本。

【题解】
对于一条路径<u,v>上的任意一点w,都有:
c o s t ( u , w ) + c o s t ( w , v ) = d i s ( u , w ) + c [ u ] − c [ w ] + d i s ( w , v ) + c [ w ] − c [ v ] = d i s ( u , v ) + c [ u ] − c [ v ] \begin{aligned} &amp;cost(u,w)+cost(w,v)\\ =&amp;dis(u,w)+c[u]-c[w]+dis(w,v)+c[w]-c[v]\\ =&amp;dis(u,v)+c[u]-c[v]\\ \end{aligned} ==cost(u,w)+cost(w,v)dis(u,w)+c[u]c[w]+dis(w,v)+c[w]c[v]dis(u,v)+c[u]c[v]
所以要想找一个点u的最优解,就可以直接从它的邻接点转移过来。
对于一个节点,它的最优目的地位置只有两种情况:在子树内;在子树外。这不是废话吗
对于子树内的情况,根据上面的分析,一发树形dp即可搞定。
对于子树外的情况,根据上面的结论,不难发现:若一个点u父亲的最优决策点不在它上,则答案为父亲的最优选择+cost(u,fa[u]),否则为父亲的次优选择+cost(u,fa[u])。
两次dfs,维护子树内最小值、次小值、子树外最小值即可。

#include<bits/stdc++.h>
using namespace std;
const int mn = 200005;
vector<int> g[mn], v[mn];
int w[mn], f[mn], d[mn], sec[mn];
bool l[mn];
inline int getint()
{
    int ret = 0, flg = 1; char c;
    while((c = getchar()) < '0' || c > '9')
        if(c == '-') flg = -1;
    while(c >= '0' && c <= '9')
        ret = ret * 10 + c - '0', c = getchar();
    return ret * flg;
}
void dfs1(int s, int fa)
{
    int m = g[s].size();
    for(int i = 0; i < m; i++)
    {
        int t = g[s][i];
        if(t != fa)
        {
            dfs1(t, s);
            if(f[s] > min(0, f[t]) + v[s][i] + w[s] - w[t])
                sec[s] = min(sec[s], f[s]), f[s] = min(0, f[t]) + v[s][i] + w[s] - w[t];
            else if(sec[s] > min(0, f[t]) + v[s][i] + w[s] - w[t])
                sec[s] = min(0, f[t]) + v[s][i] + w[s] - w[t];
        }
    }
}
void dfs2(int s, int fa, int las)
{
    if(fa)
    {
        if(f[fa] == min(0, f[s]) + las + w[fa] - w[s])
            d[s] = min(d[s], min(0, sec[fa]) + las + w[s] - w[fa]);
        else
            d[s] = min(d[s], min(0, f[fa]) + las + w[s] - w[fa]);
        d[s] = min(d[s], min(0, d[fa]) + las + w[s] - w[fa]);
    }
    int m = g[s].size();
    for(int i = 0; i < m; i++)
    {
        int t = g[s][i];
        if(t != fa)
            dfs2(t, s, v[s][i]);
    }
}
int main()
{
    int n, m, a, b, c;
    n = getint();
    for(int i = 1; i <= n; i++)
        w[i] = getint(), f[i] = d[i] = sec[i] = 1 << 30;
    for(int i = 1; i < n; i++)
        a = getint(), b = getint(), c = getint(),
        g[a].push_back(b), g[b].push_back(a),
        v[a].push_back(c), v[b].push_back(c);
    dfs1(1, 0), dfs2(1, 0, 0), m = getint();
    while(m--)
        a = getint(), printf("%d\n", min(f[a], d[a]));
}

T3

【题目描述】
简化版题意:
随机生成一个m+1个数的数列,第一个数为0, 生成第i个数时,在前i−1个数中等概率选择一个数k, 则第i 个数为k+1。数字i有一个对应的权值ai,求数列权值和的期望。

【题解】
设f[i][j]为前i个数中出现j的概率,则
f [ i ] [ j ] = ∑ k = 1 i − 1 f [ k ] [ j − 1 ] i − 1 f[i][j]=\sum_{k=1}^{i-1}\frac{f[k][j-1]}{i-1} f[i][j]=k=1i1i1f[k][j1]
所以长度为i的序列权值的期望为:
v a l [ i ] = ∑ j = 1 m f [ i ] [ j ] val[i]=\sum_{j=1}^{m}f[i][j] val[i]=j=1mf[i][j]

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mn = 100005, mm = 25, mod = 998244353;
ll a[mn], inv[mm], f[mm][mm], val[mm];
inline ll ksm(ll a, int b)
{
    ll ret = 1;
    while(b)
    {
        if(b & 1)
            (ret *= a) %= mod;
        (a *= a) %= mod, b >>= 1;
    }
    return ret;
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    for(int i = 1; i <= m; i++)
        inv[i] = ksm(i, mod - 2);
    f[1][0] = 1;
    for(int i = 2; i <= m + 1; i++)
        for(int j = 1; j < i; j++)
        {
            for(int k = 1; k < i; k++)
                (f[i][j] += f[k][j-1] * inv[i-1] % mod) %= mod;
            (val[i] += f[i][j] * a[j] % mod) %= mod;
        }
    ll ans = 0;
    for(int i = 2; i <= m + 1; i++)
        (ans += val[i]) %= mod;
    printf("%lld\n", ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值