2023年8月6日百度之星第一场初赛个人题解

2023年8月6日百度之星第一场初赛个人题解

(笨蒟蒻超级菜的,水平太低,就在这更 5 5 5 道题,剩下的也不会了,有写的不好也请见谅)

BD202301 公园

今天是六一节,小度去公园玩,公园一共 N N N 个景点,正巧看到朋友圈度度熊也在这个公园玩,于是他们约定好一块去景点 N N N。 小度当前所在景点编号为 T T T,从一个景点到附近的景点需要消耗的体力是 T E TE TE,而度度熊所在景点编号为 F F F ,移动消耗为 F E FE FE。 好朋友在一块,赶路都会开心很多,所以如果小度和度度熊一块移动(即在相同位置向相同方向移动),每一步他俩的总消耗将会减少 S S S
求他俩到景点 N N N 时,所需要的总消耗最少是多少?

格式

输入格式:

第一行三个数值, T E , F E , S TE,FE,S TE,FE,S ,分别代表小度移动消耗值,度度熊移动消耗值,一起移动的消耗减少值。 1 ≤ T E , F E , S ≤ 40000 , S ≤ T E + F E 1\le TE,FE,S\le 40000,S\le TE+FE 1TE,FE,S40000,STE+FE
第二行四个数值, T , F , N , M T,F,N,M T,F,N,M,分别代表小度出发点,度度熊出发点,目标节点,总路径数。 1 ≤ T , F , N , M ≤ 40000 1\le T,F,N,M\le 40000 1T,F,N,M40000
接下来 M M M 行,每行两个整数 X , Y X,Y X,Y,代表连通的两个景点。 1 ≤ X , Y ≤ N 1\le X,Y\le N 1X,YN

输出格式:

一个整数,即总消耗最小值。如果不能到达 N N N , 输出 − 1 -1 1

样例 1

输入:
4 4 3
1 2 8 8
1 4
2 3
3 4
4 7
2 5
5 6
6 8
7 8
输出:
22

笨蒟蒻的个人题解

题意:两人一开始分开走,然后可以汇合一起走,问总消耗最小是多少?

题目中有说,两人一块走消耗少,既然这样,假设两个人都能走到终点,那么必然存在一个点,使得两个人汇合到一块一起走到终点。那是不是就是说两个人一块走的距离越长,然后消耗越少呢?或者说两个人越早汇合,然后去终点的消耗越少?不一定哦。

在这里插入图片描述

嘿嘿,有点抽象,但应该能看的懂,红色分别代表两个人的位置,蓝色终点,很显然,如果按照上面说的两种贪心方式,这么贪心做不太可能。

所以我们不能这么想,因此,我们需要去枚举每一个位置作为中转站,从这个位置到终点是一起走的,然后两个人先走到这个地方。

先求出 T , S , N T,S,N T,S,N 为起点的最短路分别存入 d i s t 1 , d i s t 2 , d i s t 3 dist1,dist2,dist3 dist1,dist2,dist3 中,枚举每一个位置,先判断是否都能到这个地方,总消耗就是 dist1[i] * TE + dist2[i] * FE + dist3[i] * S,然后逐个的去找消耗的最小值。

注意一下,先判断两人是不是都能到达终点,不能输出 − 1 -1 1

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 4e4 + 10, M = N * 2, Mod = 1e9 + 7;

int TE, FE, S, T, F, n, m;
int e[M], ne[M], h[N], idx;
int dist1[N], dist2[N], dist3[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void bfs(int start, int dist[])
{
    queue<int> Q;
    Q.push(start);
    for(int i = 1; i <= n; i++) dist[i] = inf;
    dist[start] = 0;
    while(!Q.empty())
    {
        int t = Q.front();
        Q.pop();

        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + 1)
            {
                dist[j] = dist[t] + 1;
                Q.push(j);
            }
        }
    }
}

void solve()
{
    cin >> TE >> FE >> S >> T >> F >> n >> m;
    memset(h, -1, sizeof(h));
    for(int i = 1; i <= m; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }

    bfs(T, dist1), bfs(F, dist2);
    if(dist1[n] >= inf / 2 || dist2[n] >= inf / 2) cout << -1 << endl;
    else
    {
        int ans = 1e18;
        S = TE + FE - S;
        bfs(n, dist3);
        for(int i = 1; i <= n; i++)
        {
            if(dist1[i] >= inf / 2 || dist2[i] >= inf / 2 || dist3[i] >= inf / 2) continue;
            ans = min(ans, dist1[i] * TE + dist2[i] * FE + dist3[i] * S);
        }
        cout << ans << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}

BD202302 蛋糕划分

小度准备切一个蛋糕。这个蛋糕的大小为 N × N N\times N N×N,蛋糕每个部分的重量并不均匀。

小度一共可以切 K K K 刀,每一刀都是垂直或者水平的,现在小度想知道在切了 K K K 刀之后,最重的一块蛋糕最轻的重量是多少。

格式

输入格式:

1 1 1 行包含 N N N K K K。其中: 2 ≤ N ≤ 15 2\le N\le 15 2N15 1 ≤ K ≤ 2 × N − 2 1\le K\le 2\times N−2 1K2×N2
2 2 2 到第 1 + N 1+N 1+N 行:每行 N N N 个数字,描述这一行蛋糕每个位置的重量 W W W。其中: 0 ≤ W ≤ 1000 0\le W \le1000 0W1000

输出格式:

输出一个整数,最重的一块蛋糕最轻是多少。

样例 1

输入:
3 2
1 1 2
1 1 2
2 2 5
输出:
5

笨蒟蒻的个人题解

题意:一块蛋糕可以切 K K K 刀,然后分成若干份,问这些蛋糕最大值的重量最小值是多少?

数据范围不大,想过全排列的做法,但这样肯定 TLE, N ! N! N! 会比较大,所以不可以。

那么,显然可以得出来,答案具有二段性,那么就可以用二分找答案。

本题难点在于如何切蛋糕,怎样知道那个怎样切最好?其实很好想,先处理横着切,剩下的就是竖着切就好了,但实际写代码起来却好难。

So how?

先思考如何快速求出一个蛋糕的重量呢?暴力求解太慢,这个可以用二维前缀和来解决。

然后按照上面说的,如何处理横着切?我们可以用二进制枚举的方法,把每一种切法看成一个数字,那么这么算下来最多就是 2 15 2^{15} 215。当然得先记录横着切的位置。

枚举完横着切,然后该如何处理竖着切呢?

这里先提前介绍一下三个量, c n t , t m p cnt,tmp cnt,tmp v i s vis vis c n t cnt cnt 表示横着的每一列的每一个区间的蛋糕重量和, t m p tmp tmp 代表每一列的蛋糕重量, v i s vis vis 表示哪些列需要切。

首先先找一块蛋糕的重量记录 r e s res res,如何记录的前提也是要先知道位置,这里也要拿变量去记录。然后拿 c n t cnt cnt 不断累加区间蛋糕的重量和,同时 t m p tmp tmp 去记录每一块的区间和,假设二分的答案是 w w w,那么,当 c n t > w cnt > w cnt>w 时,即发现了蛋糕重量过大,然后我们就要改刀了,不能接着切这一列,换成前面一列,此时就要用 t m p tmp tmp 去更新 c n t cnt cnt 的值,这也意味着就是要改刀了,然后同时记录前一列的 v i s vis vis 的值,然后发现之前如果已经改过刀了,那么 c n t cnt cnt 就不必再次累加了,这就意味着是要重新切了。

那么如何判断答案成立,或是不成立的呢?这么多种横着切的情况,只要有一种情况是小于或等于 K K K 的,那么就成立。

OK,该讲的都讲完了……

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 2e1 + 10, M = N * 2, Mod = 1e9 + 7;

int n, k;
int a[N][N], f[N][N];
int cnt[N], tmp[N], vis[N];

bool check(int w)
{
    for(int i = 0; i < (1 << n); i++)
    {
        memset(vis, 0, sizeof(vis));
        memset(cnt, 0, sizeof(cnt));
        memset(tmp, 0, sizeof(tmp));

        vector<int> v;
        for(int d = i, p = 1; d; p++, d >>= 1)
            if(d & 1) v.push_back(p);

        if((int)v.size() > k) continue;
        int ans = (int)v.size();
        v.push_back(n);

        bool flag = false;
        for(int j = 1; j <= n; j++)
        {
            int top = 1, down = 0, p = 0;
            for(int x: v)
            {
                down = x;
                int res = f[down][j] - f[down][j - 1] - f[top - 1][j] + f[top - 1][j - 1];
                if(res > w) flag = true;
                if(flag) break;
                cnt[++p] += res;
                if(vis[j - 1]) cnt[p] = res;
                tmp[p] = res;
                if(cnt[p] > w)
                {
                    vis[j - 1] = 1;
                    for(int u = 1; u <= p; u++) cnt[u] = tmp[u];
                }
                top = down + 1;
            }
            if(flag) break;
        }
        if(flag) continue;
        for(int j = 1; j < n; j++)
            if(vis[j]) ans++;
        if(ans <= k) return true;
    }
    return false;
}

void solve()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j];
        }

    int l = 0, r = inf;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}

BD202303 第五维度

零维是点,点动成线;

一维是线,线动成面;

二维是面,面动成体;

三维是体,体动成史;

四维是史,史动???

现在人类企图理解第五维度。

而小度现在是第五维度的一位智者。一天,小度发现人类的许多科学家在试图理解第五维度,人类是四维生物,若是他们理解了第五维度,很可能也会到来第五维度的空间,这显然是小度不愿意看到的(毕竟哪里都有人口数量的问题….)所以小度希望他们尽可能晚的理解第五维度,因此,小度用更高维度的视角把所有人类中在理解第五维的科学家都看到了,而这些科学家的智商会不一样,所以他们的理解速度 V i V_i Vi 也会不一样;并且,他们开始理解的时间点 S i S_i Si 也不一样。理解速度 V i V_i Vi 描述为每过单位时间可获得 V i V_i Vi 个单位理解力,也就是说在 S i + 1 S_i+1 Si+1 的时间点该科学家会第一次贡献 V i V_i Vi 的理解力。我们定义理解力总数超过 m m m 时理解了第五维度。 小度因为维度更高,可以使用时间悖论来给人类一次重大的打击,小度可以让任意一位科学家在任意一个时间点消失,所以他接下来的理解不会继续;而且人类不会记得他,所以他之前的贡献会消失。因为小度能力有限,所以小度只能使用一次暂时悖论。

现在求在尽可能晚的情况下,人类理解第五维度的最早时间点。

时间点初始为 0 0 0,但显然,没有科学家能够在 0 0 0 时刻有贡献。

格式

输入格式:

第一行给出一个整数 n n n 和一个整数 m m m ,表示有 n n n 个科学家,在理解力总数超过 m m m 时理解了第五维度;
第二至 n + 1 n+1 n+1 行:每行两个整数 S i S_i Si​ 和 V i V_i Vi​;
对于 100 100 100% 的数据: 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105 m ≤ 2 × 1 0 9 m\le 2\times 10^9 m2×109
对于 100 100 100% 的数据: 0 ≤ S i ≤ 2 × 1 0 9 0\le S_i \le 2\times 10^9 0Si2×109 0 ≤ V i ≤ 1 0 3 0\le V_i\le 10^3 0Vi103

输出格式:

一行,包含一个整数 T T T 表示在尽可能晚的情况下,人类理解第五维度的最早时间点。
若是人类永远无法理解第五维度,则输出 − 1 -1 1

样例 1

输入:
3 10
0 1
4 6
5 1
输出:
8

样例 2

输入:
3 10
0 0
4 0
5 1
输出:
-1

备注

对于第一个样例,使得 S i = 4 , V i = 6 S_i=4,V_i=6 Si=4,Vi=6 该科学家消失,则每个时刻总共理解力为: 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 7 7 7 9 9 9 11 11 11 ,在时刻 8 8 8 超过 m = 10 m=10 m=10 ,因此输出 8 8 8
对于第二个样例,人类永远无法理解第五维度,因此输出 − 1 −1 1

笨蒟蒻的个人题解

题意: n n n 个科学家,每个开始理解的时间是 S i S_i Si,理解速度是 V i V_i Vi,UFO 先生可以使理解最多的那个科学家消失,然后问啥时候理解总和超过 m m m

观察发现,答案具有二段性,所以可以用二分。

这样二分答案,然后统计结果的时候减掉贡献最大的去判断就好了。

注意:一定要在加的时候就判断,不然好像爆 ll 了,我就不明所以然后 WA 了一发,还有,别忘记特判 0 0 0 的情况。

啥时候无解?就是最多只有一个科学家做贡献。

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 2e5 + 10, M = N * 2, Mod = 1e9 + 7;

int n, m, cnt;
PII p[N];

bool check(int x)
{
    int res = 0, mx = 0;
    for(int i = 1; i <= n; i++)
    {
        if(p[i].fi < x)
        {
            res += p[i].se * (x - p[i].fi);
            mx = max(mx, p[i].se * (x - p[i].fi));
            if(res - mx >= m) return false;
        }
    }
    return true;
}

void solve()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> p[i].fi >> p[i].se, cnt += (p[i].se != 0);

    if(m == 0)
    {
        cout << 0 << endl;
        return;
    }

    if(cnt <= 1) cout << -1 << endl;
    else
    {
        int l = 0, r = 1e18;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(check(mid)) l = mid + 1;
            else r = mid;
        }
        cout << l << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}

BD202305 糖果促销

小度最喜欢吃糖啦!!!
这天商店糖果促销,可给小度高兴坏了。

促销规则:一颗糖果有一张糖纸, p p p 张糖纸可以换取一颗糖果。换出来糖果的包装纸当然也能再换糖果。

小度想吃 k k k 颗糖果,他需要买多少颗糖?

格式

输入格式:

第一行一个整数 T ( 1 ≤ T ≤ 1 0 6 ) T(1\le T\le 10^6) T(1T106) ,表示测试数据组数;
接下来 T T T 行,每行两个整数 p i , k i ( 1 ≤ p i ≤ 1 0 9 , 0 ≤ k i ≤ 1 0 9 ) p_i,k_i(1\le p_i\le 10^9,0\le k_i\le 10^9) pi,ki(1pi109,0ki109) ,表示第 i i i 次测试中, p i p_i pi​ 张糖纸换一颗糖,小度想吃 k i k_i ki​ 颗糖。

输出格式:

T T T 行,每行一个整数表示需要买多少颗糖果。

样例 1

输入:
3
3 4
4 5
2 7
输出:
3
4
4

笨蒟蒻的个人题解

题意:颗糖果有一张糖纸, p p p 张糖纸可以换取一颗糖果。吃 k k k 颗糖果需要多少糖?

很明显,答案具有二段性,直接二分,没啥说的。

注意特判 0 0 0 1 1 1 就好。

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 2e5 + 10, M = N * 2, Mod = 1e9 + 7;

int p, k;

bool check(int x)
{
    int res = x;
    while(x >= p)
    {
        res += x / p;
        x = x / p + x % p;
    }
    if(res >= k) return false;
    return true;
}

void solve()
{
    cin >> p >> k;

    if(k == 0)
    {
        cout << 0 << endl;
        return;
    }

    if(p == 1)
    {
        cout << 1 << endl;
        return;
    }

    int l = 0, r = 1e9;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) l = mid + 1;
        else r = mid;
    }
    cout << l << endl;
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
    cin >> T;
    while(T--)
        solve();
    return 0;
}

BD202307 数字串

小度有一个非常长的数字串 s s s,长度为 n n n,并且数字串中不包含字符 ‘0’

现在小度需要在这个数字串中添加恰好 k k k‘+’ 号,变成一个表达式 。
例如,如果 s=“123” k = 1 k=1 k=1 , 那么小度可以得到 “12+3” 或者 “1+23”,两种可能的表达式的值分别为 15 15 15 24 24 24

小度需要使得添加恰好 k k k‘+’ 号之后的表达式的值尽量大,并输出这个最大值。

格式

输入格式:

第一行包含两个正整数 n , k ( 0 ≤ k < n ≤ 1 0 6 ) n,k(0≤k<n≤10^6) n,k(0k<n106) n n n 表示有数字串的长度, k k k 表示需要添加的加号数量;
第二行包含一个长度为 n n n 的数字串。

输出格式:

输出仅一行,一个整数表示最大的表达式的值。

样例 1
输入:
3 1
123
输出:
24

笨蒟蒻的个人题解

题意:在一个长度为 n n n 的字符串上添加 k k k 个加号,使其值最大。

(一开始我还想着搞 DP,哎,是我太蒟蒻了

how?

大胆假设,一定是一个主体然后加上几个一位数字的情况。

why?

举个例子,比如说: n = 1234 , k = 1 n=1234,k=1 n=1234,k=1 它的结果一定是 1 + 234 1+234 1+234 或者是 123 + 4 123+4 123+4 这样,不可能是 12 + 34 12+34 12+34。为什么捏?因为假设加的数字超过了一位的话,那么主体部分一定会减少掉这些被超过的位数。这里就是主体部分是 3 3 3 位,然后如果加的部分是 2 2 2 位数字就不行了,这样的话主体部分就变成了 2 2 2 位,所以一定是一个主体然后加 k k k 个一位数字。

understand 这些,然后我们开始贪心,如何贪心呢?我们贪主体部分最大的,不管加上的一位数。

然后为啥这样可以?假设主体部分是 a b c d e f g abcdefg abcdefg k = 3 k=3 k=3,然后我们的贪心结果应该是 d e f g defg defg,这样最大了,主体部分是有权值的分别是 1000 1000 1000 100 100 100 10 10 10 1 1 1,其余的个位数权值也是 1 1 1,因为主体所带的权值大,所以无论怎么弄,要想让总的值最大,必须主体最大。

然后就是如何找主体最大部分了?……

主体部分总共有 n − k n-k nk 位,我试过用暴力,就是遍历所有的字符串,然后去比较找出最大的。就像下面写的这样去一一比较的:

string mx = s.substr(0, n - k);
int mxp = 0;
for(int i = 1; i < n - k; i++)
    if(s.substr(i, n - k) > mx)
    {
        mxp = i;
        mx = s.substr(i, n - k);
    }

可是这样 TLE 了,因为 n n n 最大是 1 0 6 10^6 106,然鹅,这里看似是 O ( n ) O(n) O(n),其实不然,字符串的比较是一位一位去比的,这里也是 O ( n ) O(n) O(n),所以这里其实是 O ( n 2 ) O(n^2) O(n2),因此简单的比较根本上不可行。

咋办呢?这里可以用字符串哈希和 l c p lcp lcp 来求解。

先用字符串哈希来简单的预处理一下字符串,就是把字符串看成一个数字,然后前缀和一下就能在 O ( 1 ) O(1) O(1) 的时间里搞出任意区间 [ l , r ] [l,r] [l,r] 的字符串值。

pr[0] = 1;
for(int i = 1; i <= n; i++)
{
    hs[i] = (hs[i - 1] * R + s[i]) % Mod;
    pr[i] = pr[i - 1] * R % Mod;
}
int getx(int l, int r)
{
    return (hs[r] - hs[l - 1] * pr[(r - l + 1)] % Mod + Mod) % Mod;
}

h s 、 p r hs、pr hspr 分别表示字符串前缀和数和进制数,然后获取就是 [ l , r ] [l,r] [l,r] 的区间就是 (hs[r] - hs[l - 1] * pr[(r - l + 1)] % Mod + Mod) % Mod; 。嗯,对。

然后字符串哈希这种方法,只能去保证你那个字符串是否相等,并不能保证谁大谁小呀!

那咋办?

我们假设 A = a b c d e , B = a b c d d A = abcde, B = abcdd A=abcde,B=abcdd,嗯哼?用你那智慧、帅气而又靓丽的眼睛一看就知道是 A > B A>B A>B 吧!仔细发现他们的公共前缀 a b c d abcd abcd 是相同的,其实我们只需要比较最后一位的字符是否相同就行了吧,聪明的应该一眼都能看出吧。

然后我们求解 l c p lcp lcp,那么可以发现, a b c d abcd abcd 所哈希的值一定是一样的,而往后呢?就会发生改变!因此在第 5 5 5 位这个位置非常的关键!

那如何快速的找出这个位置呢?这个位置是具有二段性的,因为 1 ∼ 4 1\sim 4 14 位哈希值是相同的,而第 5 5 5 位往后呢,它的哈希值全都是不一样的。这样的话我们可以又用二分来求解。用二分来找出那个位置,然后比较后面的就好了。这两个串无公共前缀,那么就是 0 0 0,直接比第一位就好了。

就这样一直找主体最大的那个部分,更新最大值。由于这数字巨大,最多有 1 0 6 10^6 106 的位数,因此,还要用上高精度来套模板弄(真是麻烦)……

个人觉得本题难度好大(可能我是蒟菜会这么想……),难点主要还是在求字符串主体最大这块。

题外话:

我来小小的拓展一下:假如加号改成乘号,然后 n n n 范围改小一点,这样子的题,就不能用字符串哈希了,那么这道题其实就是区间 DP 了。

how?

三维 DP, d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示第 i − > j i->j i>j 的区间放 k k k 个乘号的集合中的最大值,这里要枚举一下 i 、 j i、j ij 中间的位置 u u u,假设左边的乘号个数 s l sl sl,那么右边乘号个数就是 s r sr sr,并且满足 s l , s r ≥ 0 , s l + s r = = k sl,sr\ge0,sl+sr==k sl,sr0,sl+sr==k

然后转移方程就是 d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i ] [ u − 1 ] [ s l ] × d p [ u ] [ j ] [ s r ] ) dp[i][j][k] = max(dp[i][j][k],dp[i][u - 1][sl]\times dp[u][j][sr]) dp[i][j][k]=max(dp[i][j][k],dp[i][u1][sl]×dp[u][j][sr])

答案就是 d p [ 1 ] [ n ] [ k ] dp[1][n][k] dp[1][n][k]

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 1e6 + 10, M = N * 2, Mod = 1e9 + 7, R = 131;

int n, k, w;
string s;
int hs[N], pr[N];

vector<int> add(vector<int> A, vector<int> B)
{
    if(A.size() < B.size()) return add(B, A);

    vector<int> C;
    int t = 0;
    for(int i = 0; i < A.size(); i++)
    {
        t += A[i];
        if(i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }

    if(t) C.push_back(t);
    return C;
}

int getx(int l, int r)
{
    return (hs[r] - hs[l - 1] * pr[(r - l + 1)] % Mod + Mod) % Mod;
}

int lcp(int x, int y)
{
    int l = 1, r = n - k;
    while(l < r)
    {
        int mid = l + r + 1 >> 1;
        if(getx(x, x + mid - 1) == getx(y, y + mid - 1)) l = mid;
        else r = mid - 1;
    }
    if(getx(x, x + l - 1) == getx(y, y + l - 1)) return l;
    return 0;
}

void solve()
{
    cin >> n >> k >> s; s = ' ' + s;

    pr[0] = 1;
    for(int i = 1; i <= n; i++)
    {
        hs[i] = (hs[i - 1] * R + s[i]) % Mod;
        pr[i] = pr[i - 1] * R % Mod;
    }

    int mxp = 1;
    for(int i = 2; i <= k + 1; i++)
    {
        int j = lcp(i, mxp);
        if(j == n - k) continue;
        if(s[mxp + j] < s[i + j]) mxp = i;
    }

    for(int i = 1; i < mxp; i++) w += s[i] - '0';
    for(int i = mxp + n - k; i <= n; i++) w += s[i] - '0';

    vector<int> a, b;
    for(int i = mxp + n - k - 1; i >= mxp; i--) a.push_back(s[i] - '0');
    while(w)
    {
        b.push_back(w % 10);
        w /= 10;
    }

    a = add(a, b);

    for(int i = a.size() - 1; i >= 0; i--) cout << a[i];
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}
  • 32
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值