封面图源 【动漫作业本】:https://space.bilibili.com/488779255

[竞赛地址] https://ac.nowcoder.com/acm/contest/82345

E. Traffic Lights

给定n个点m条边。从s出发,目的地为t

每条边有需要花费的时间。

车每次从停止到启动需要花费5秒。

每个节点有红绿黄灯,只有绿灯和红灯可以通行。

问,到达t需要的最少时间。

数据范围

在这里插入图片描述
红绿灯持续时间
在这里插入图片描述

每条边花费时间
在这里插入图片描述

D. Merge Fruits

有n个水果,每个水果有特性ai,bi,合并左右两个相邻水果(ai,bi),(aj,bj)时,会释放ai*bj的能量,并得到新的水果(aj,bi)

现可以选择若干水果,将它们的权重乘以k,即修改后的水果(ai,bi),变为(k*ai,k*bi)

至少选择一个水果进行权值的修改。

问,将这n个水果合并成一个水果后(合并顺序任意),最多可以得到多少能量。

E. Traffic Lights(图论/最短路径/dijkstra)

就是最短路径加上了等红绿灯、车启动需要5秒的逻辑。

贪心策略还是保持不变,对于每个节点,尽量得取到达需要的最短时间。

如果此时处于红灯时间,则加上等待时间和车启动时间。

为什么取最短时间最优。因为,对于一个节点,越早到达,它的启动时间(处于绿灯和黄灯)就越早开启。也就是说,它总是不劣于任意到达时间更晚的时刻。

#define ll long long
#define inf 1e18
const int maxn = 10010;
#define pii pair<int, int>
#define f first
#define s second
#define mp make_pair

int n, m, s2, t;
int u, v, w;
int h[maxn][4]; // h[i][j], g, y, r的时间, h[i][4]为 g + y + r
vector<pii> ve[maxn];

ll dis[maxn];
bool vis[maxn];
void dijkstra() {
    for (int i = 0; i < n; ++i) {
        dis[i] = inf;
        vis[i] = 0;
    }
    priority_queue<pii, vector<pii>, greater<pii> > q;
    dis[s2] = 5; // 启动需要5秒
    q.push(mp(dis[s2], s2));
    while (!q.empty()) {
        pii cur = q.top();
        q.pop();
        u = cur.s;
        if (vis[u]) {
            continue;
        }
        vis[u] = 1;
        for (auto edge: ve[u]) {
            v = edge.f;
            w = edge.s;
            int tmp = (cur.f + w) % h[v][3], d = cur.f + w;
            // 到达该点,且准备好出发需要的时间
            // 注意特判终点,此时无需启动
            if (v != t && tmp >= h[v][0] + h[v][1]) {
                d += h[v][3] - tmp + 5;
            }
            
            if (dis[v] > d) {
                dis[v] = d;
                q.push(mp(d, v));
            }
        }
    }
}
void solve() {
    for (int i = 0; i < n; ++i) {
        h[i][3] = 0;
        for (int j = 0; j < 3; ++j) {
            scanf("%d", &h[i][j]);
            h[i][3] += h[i][j];
        }
        ve[i].clear();
    }
    while (m--) {
        scanf("%d%d%d", &u, &v, &w);
        ve[u].push_back(mp(v, w));
        ve[v].push_back(mp(u, w));
    }
    dijkstra();
    printf("%lld:%02lld\n", dis[t] / 60, dis[t] % 60);
}

D. Merge Fruits(动态规划)

观察发现,没有添加权重时,原始的答案值为

a[1]*b[2]+a[2]*b[3]+...+a[n-1]*b[n]

添加了权值后,那么就相当于,上述的数列,添加上若干k或者k*k的系数。

容易发现这是一个线性dp

定义dpi表示第i项时,系数为k^j。

就可以愉快的递推了。

注意,题目要求至少改变一个点,所以我们需要单独特判下res = 只修改一个点的情况。

最后再从dpn, dpn,res中取最大值即可。

详见代码注释

#define ll long long
#define inf 1e18
const int maxn = 100010;

int n;
int a[maxn], b[maxn];
ll dp[maxn][3]; // 0, 1, 2
ll ans, k;
void solve() {
    scanf("%d%lld", &n, &k);
    
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &a[i], &b[i]);
    }
    ll all = 0;
    for (int i = 2; i <= n; ++i) {
        all += a[i-1] * b[i];
    }
    ans = -inf;
    for (int i = 1; i <= n; ++i) { // 特判 只改变 一个点的 答案值
        ll tmp = a[i-1] * b[i] + a[i] * b[i+1];
        ans = max(ans, all - tmp + k * tmp);
    }
    
    // init
    dp[0][0] = 0;
    dp[0][1] = dp[0][2] = -inf;
    
    dp[1][0] = 0;
    dp[1][1] = -inf; //  
    dp[1][2] = -inf; 
    
    ++n; // 需要枚举到第n+1个点,不然会漏了情况
    for (int i = 2; i <= n; ++i) {
        dp[i][0] = dp[i-1][0] + a[i-1] * b[i];
        dp[i][1] = dp[i-1][1] + a[i-1] * b[i];
        dp[i][2] = dp[i-1][2] + k * k * a[i-1] * b[i];
        
        // 第i-2个位置系数为2,那么此时第i-1个位置系数为1,第i个位置没有添加k,系数为0
        dp[i][1] = max(dp[i][1], dp[i-2][2] + k * a[i-2] * b[i-1] + a[i-1] * b[i]);
        // 第i-2个位置系数为0,第i个位置系数为2,此时第i-1个位置系数只能为1
        dp[i][2] = max(dp[i][2], dp[i-2][0] + k * a[i-2] * b[i-1] + k * k * a[i-1] * b[i]);
    }
    ans = max(ans, max(dp[n][1], dp[n][2]));
    printf("%lld\n", ans);
}

往期文章

2024年国际大学生程序设计竞赛(ACM-ICPC)新疆赛区大赛(AIFBC):https://zhuanlan.zhihu.com/p/698684625

2024年国际大学生程序设计竞赛(ACM-ICPC)新疆赛区大赛(GH):https://zhuanlan.zhihu.com/p/699108788

2024年国际大学生程序设计竞赛(ACM-ICPC)新疆赛区大赛(J):https://zhuanlan.zhihu.com/p/699535119


对方正在debug
1 声望0 粉丝