惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
MyScale Blog
MyScale Blog
Y
Y Combinator Blog
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
V
Vulnerabilities – Threatpost
T
The Blog of Author Tim Ferriss
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
N
News and Events Feed by Topic
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 【当耐特】
N
Netflix TechBlog - Medium
博客园 - 叶小钗
B
Blog
Vercel News
Vercel News
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
F
Fortinet All Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
S
Securelist
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Palo Alto Networks Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
DataBreaches.Net
Cyberwarzone
Cyberwarzone
Engineering at Meta
Engineering at Meta
Martin Fowler
Martin Fowler
G
GRAHAM CLULEY
Project Zero
Project Zero
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
CERT Recently Published Vulnerability Notes
L
LangChain Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Check Point Blog
A
About on SuperTechFans
W
WeLiveSecurity
The GitHub Blog
The GitHub Blog

博客园 - 糖豆爸爸

【树上DP前导知识汇总】 快速幂、龟速乘总结 反向建图+拓扑排序 卡特兰数专题(Catalan) AcWing 126. 最大的和 AcWing 431. 守望者的逃离 AcWing 468. 魔法阵 AcWing 463. 求和 洛谷 P1632 点的移动 P1056 NOIP2008 普及组 排座椅 洛谷 P1889 士兵站队 洛谷 P1862 输油管道问题 CF444C DZY Loves Colors P2253 好一个一中腰鼓! SCOI2010 P2572 序列操作 P4344 SHOI2015 脑洞治疗仪 T125847 【模板】动态开点线段树 P3373 【模板】线段树 2 Physical Education Lessons
AcWing 414. 数字游戏
糖豆爸爸 · 2023-09-27 · via 博客园 - 糖豆爸爸

\(AcWing\) \(414\). 数字游戏

一、题目描述

丁丁最近沉迷于一个数字游戏之中。

这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。

游戏是这样的,在你面前有一圈整数(一共 \(n\) 个),你要按顺序将其分为 \(m\) 个部分,各部分内的数字相加,相加所得的 \(m\) 个结果对 \(10\) 取模后再相乘,最终得到一个数 \(k\)

游戏的要求是使你所得的 \(k\) 最大或者最小。

例如,对于下面这圈数字(\(n=4,m=2\)):

当要求 最小值 时,\(((2−1)\ mod \ 10)×((4+3) \ mod\ 10)=1×7=7\)
当要求 最大值 时,\(((2+4+3)\ mod\ 10)×(−1\ mod\ 10)=9×9=81\)

特别值得注意的是,无论是负数还是正数,对 \(10\) 取模的结果均为非负值。

丁丁请你编写程序帮他赢得这个游戏。

输入格式
输入文件第一行有两个整数,\(n\)\(m\)

以下 \(n\) 行每行一个整数,其绝对值不大于 \(10000\),按顺序给出圈中的数字,首尾相接。

输出格式
输出文件有两行,各包含一个非负整数。

第一行是你程序得到的最小值,第二行是最大值。

数据范围
\(1≤n≤50,1≤m≤9\)

输入样例

4 2
4
3
-1
2

输出样例

7
81

二、题目解析

(\(DP\),区间\(DP\)) \(O(n^3m)\)

首先 将环从起点处断开,然后复制一遍接在后面,这样原问题就转化成了线段型的模型。

注:破环成链

n = read(), m = read();
for (int i = 1; i <= n; i ++ ){
    w[i] = read();
    w[i + n] = w[i]; //破环成链
}

然后 从集合角度来分析状态表示和状态计算

状态表示

\(f[l][r][k]\):所有将区间\([l,r]\)划分成\(k\)部分方案的乘积 最大值
\(g[l][r][k]\):所有将区间\([l,r]\)划分成\(k\)部分方案的乘积 最小值

状态计算

考虑\(f[l][r][k]\)如何计算获得,关键是寻找 集合划分的依据,划分依据一般选取 最后一步的操作,所以这里我们可以 按最后一部分的位置来将\(f[l][r][k]\)所表示的所有方案划分成若干类

注:上面的是数字的点,而不是区间,不要混淆!可能会造成理解错误~

注:第\(k\)个部分,肯定是从\(k-1\)个部分转移而来,现在考虑的是划分完\(k-1\)部分后,最后一个点的所有可能性\([l+k-2,r-1]\)

不妨设原\(k-1\)部分的终点是\(j\),所以

\[\large l+k-2<=j<=r-1 \]

注:\(j\)是可以遍历的

思考增加了第\(k\)个部分,最大值、最小值怎么求呢?

\(k-1\)个部分的终点是\(j\),第\(k\)个部分的起点是从\([j+1,r]\)的任意一个值。
就是

\[\large f[l][r][k]= f[l][j][k-1] * (sum(r)-sum(j+1)) \\ \large g[l][r][k]= g[l][j][k-1] * (sum(r)-sum(j+1))\]

解释
因为对于每个方案而言,它的得分就是前面的数乘上后面的数。
由于每个数都是非负的,如果我希望我最终的结果是最大值或最小值,而且,现在后面黄色方框中的数字大小是固定的,所以,前面最大就是整体最大,前面最小就是整体最小。

最终枚举所有长度是\(n\)的区间,取最大值/最小值即可。
\(\large f[i][i+n][m] \\ \large g[i][i+n][m]\)

时间复杂度
状态总共有 \(O(n^2m)\) 个,计算每个状态需要 \(O(n)\) 的计算量,因此总时间复杂度是 \(O(n^3m)\)

三、实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 10, INF = 0x3f3f3f3f;

int n, m;
int w[N], s[N];
int f[N][N][M], g[N][N][M];

int get_mod(int x) {
    return (x % 10 + 10) % 10;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> w[i];
        w[i + n] = w[i]; // 破环成链
    }
    for (int i = 1; i <= n * 2; i++) s[i] = s[i - 1] + w[i]; // 预处理前缀和,下标从1开始

    memset(f, 0x3f, sizeof f);  // 预求最小,先设最大
    memset(g, -0x3f, sizeof g); // 预求最大,先设最小

    for (int len = 1; len <= n; len++)               // 区间DP,先枚举长度
        for (int l = 1; l + len - 1 <= n * 2; l++) { // 枚举起点
            int r = l + len - 1;                     // 计算终点
            // DP 初始化
            // 边界条件是全部划分为k=1,也就是一块时的结果是多少,根据题意,一整块当然就是区间内容相加后对10取模
            f[l][r][1] = g[l][r][1] = get_mod(s[r] - s[l - 1]);
            // 状态转移
            for (int k = 2; k <= m; k++)                   // 枚举每个可以划分成的部分
                for (int j = l + k - 2; j <= r - 1; j++) { // 枚举K-1部分时的最后一个终点位置
                    f[l][r][k] = min(f[l][r][k], f[l][j][k - 1] * get_mod(s[r] - s[j]));
                    g[l][r][k] = max(g[l][r][k], g[l][j][k - 1] * get_mod(s[r] - s[j]));
                }
        }

    // 枚举每个区间长度为n的区间,获取符合长度要求的区间内,划分成m块的最大、最小值
    int mx = -INF, mi = INF;
    for (int i = 1; i <= n; i++) {
        mx = max(mx, g[i][i + n - 1][m]);
        mi = min(mi, f[i][i + n - 1][m]);
    }
    cout << mi << endl;
    cout << mx << endl;
    return 0;
}