2021-2022-1 ACM集训队每周程序设计竞赛(3)题解

A - 苹果派

题意
你有 a 个苹果和 p 个苹果片 你有a个苹果和p个苹果片 你有a个苹果和p个苹果片
1 个苹果可以制作 3 个苹果片 1个苹果可以制作3个苹果片 1个苹果可以制作3个苹果片
2 个苹果片可以制作 1 个苹果派 2个苹果片可以制作1个苹果派 2个苹果片可以制作1个苹果派
问一共可以制作多少个苹果派 问一共可以制作多少个苹果派 问一共可以制作多少个苹果派
0 < = a , p < = 100 0 <= a , p <= 100 0<=a,p<=100
思路
模拟 模拟 模拟
时间复杂度 O 1 O1 O1

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e6 + 10 , M = 2010 , mod = 1e9 + 7 ;
 
signed main()
{
    int n , p ;
    cin >> n >> p ;
    
    p += 3 * n ;
    
    de(p/2) ;
    
    return 0;
}
 

B - 餐馆指南

题意
你决定写一本介绍好餐馆的书。 你决定写一本介绍好餐馆的书。 你决定写一本介绍好餐馆的书。
你想介绍的餐厅有 N 家 : 餐厅 1 ,餐厅 2 , . . . 餐厅 n 你想介绍的餐厅有N家 : 餐厅1,餐厅2,...餐厅n 你想介绍的餐厅有N:餐厅1,餐厅2...餐厅n
1 < = n < = 100. 1 <= n <= 100. 1<=n<=100.

餐厅 i 在 S i 市, 餐厅i在Si市, 餐厅iSi市,
你对每一家餐厅都有一个得分 p 你对每一家餐厅都有一个得分p 你对每一家餐厅都有一个得分p
1 < = p < = 100 1 <= p <= 100 1<=p<=100
没有两家餐馆得分相同。 没有两家餐馆得分相同。 没有两家餐馆得分相同。

您要按以下顺序介绍餐厅 : 您要按以下顺序介绍餐厅: 您要按以下顺序介绍餐厅:
餐厅按其城市名称的字典顺序排列。 餐厅按其城市名称的字典顺序排列。 餐厅按其城市名称的字典顺序排列。
如果同一个城市有多家餐厅, 如果同一个城市有多家餐厅, 如果同一个城市有多家餐厅,
则按得分降序排列。 则按得分降序排列。 则按得分降序排列。

按照书中介绍的顺序打印餐厅的编号。 按照书中介绍的顺序打印餐厅的编号。 按照书中介绍的顺序打印餐厅的编号。
思路
结构体自定义排序 结构体自定义排序 结构体自定义排序
时间复杂度 O n l o g n Onlogn Onlogn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e6 + 10 , M = 2010 , mod = 1e9 + 7 ;

struct ai{
    int id , score ; // 编号 , 得分
    string city ; // 城市名称
}q[M] ;

bool cmp(ai a , ai b)
{
    if(a.city == b.city) return a.score > b.score ;  // 城市字典序相等的时候按照得分大于(逆序)排序
    else return a.city < b.city ;    //  否则按照字典序小于(顺序)排序
}

signed main()
{
    int n ;
    cin >> n ;
    
    fer(i,1,n)
    {
        cin >> q[i].city >> q[i].score ;
        q[i].id = i ;
    }
    
    sort(q + 1 , q + 1 + n , cmp) ;
    
    fer(i,1,n) 
    {
        cout << q[i].id << "\n" ;
    }
    
    return 0;
}

C - 开关

题意
我们有“开”和“关”状态的 n 个开关和 m 个灯泡。 我们有“开”和“关”状态的n个开关和m个灯泡。 我们有状态的n个开关和m个灯泡。
开关编号为 1 到 n , 开关编号为1到n, 开关编号为1n
灯泡编号为 1 到 m 。 灯泡编号为1到m。 灯泡编号为1m

灯泡 i 连接了 k i 个开关 : 开关 s i 1 , s i 2 , . . . ,和 s i k i 。 灯泡i连接了ki个开关:开关si1,si2,...,和siki。 灯泡i连接了ki个开关:开关si1si2...,和siki
对于灯泡 i 连接的这些开关中如果打开的开关数量 m o d 2 等于 p [ i ] ,灯泡 i 会被点亮。 对于灯泡i连接的这些开关中如果打开的开关数量mod2等于p[i],灯泡i会被点亮。 对于灯泡i连接的这些开关中如果打开的开关数量mod2等于p[i],灯泡i会被点亮。

开关的“开”和“关”状态有多少种组合可以点亮所有灯泡? 开关的“开”和“关”状态有多少种组合可以点亮所有灯泡? 开关的状态有多少种组合可以点亮所有灯泡?
1 < = n , m < = 10 1 <= n , m <= 10 1<=n,m<=10
1 < = k i < = n 1 <= ki <= n 1<=ki<=n
p [ i ] = 0 / 1 p[i] = 0 / 1 p[i]=0/1
思路
考虑 n 的范围最大只有 10 考虑n的范围最大只有10 考虑n的范围最大只有10
我们可以暴力枚举所有开关打开或者关闭的状态 我们可以暴力枚举所有开关打开或者关闭的状态 我们可以暴力枚举所有开关打开或者关闭的状态

考虑使用状态压缩来做 考虑使用状态压缩来做 考虑使用状态压缩来做
用一个二进制数来表示当前 n 个开关的打开状态 用一个二进制数来表示当前n个开关的打开状态 用一个二进制数来表示当前n个开关的打开状态

假设现在的二进制数是 010101 假设现在的二进制数是010101 假设现在的二进制数是010101
0 表示没选这个开关, 1 表示选了这个开关 0表示没选这个开关,1表示选了这个开关 0表示没选这个开关,1表示选了这个开关
选了 1 的开关即为第 1 个第 3 个第 5 个 选了1的开关即为第1个第3个第5个 选了1的开关即为第1个第3个第5

从 0 到 2 n − 1 ( n 个 1 ) 包含了所有的可能性 从0到2^n-1(n个1)包含了所有的可能性 02n1(n1)包含了所有的可能性
时间复杂度 O 10 ∗ 10 ∗ 2 10 O10*10*2^{10} O1010210

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e6 + 10 , M = 2010 , mod = 1e9 + 7 ;

int n , m ; // n个开关 m个灯泡
vector<int> q[M] ;
int p[M] ;

signed main()
{
    cin >> n >> m ;
    
    fer(i,1,m)
    {
        int k ;
        sf(k) ;
        
        fer(j,1,k)
        {
            int x ;
            sf(x) ;
            q[i].pb(x) ;
        }
    }
    
    fer(i,1,m) sf(p[i]) ;
    
    int res = 0 ;  // res 表示最后的答案
    
    for(int i = 0 ; i < 1 << n ; i ++)  // 枚举所有可能的二进制状态
    {
        bool st[11] = {0} ;             //  初始化所有开关都关闭
        for(int j = 0 ; j < n ; j ++)   //  判断哪些开关开了
        {
        	// 如果i这个二进制数的第j位是1的话
            if(i >> j & 1) st[j+1] = 1 ;  // 开关编号从1开始  所以+1
        }
        
        int cnt = 0 ;  // cnt表示当前打开的灯泡数量
        for(int j = 1 ; j <= m ; j ++)  //  枚举所有灯泡  判断当前这个灯泡是否打开
        {
            int k = 0 ;         //  k表示当前这个灯泡有几个对应的开关打开
            for(int z = 0 ; z < q[j].size() ; z ++)  // 枚举这个灯泡的所有开关
            {
                if(st[q[j][z]]) k ++ ;   //  如果这个开关打开了 k ++
            }
            if(k % 2 == p[j]) cnt ++ ;  //   如果 k % 2 == p[j] 说明这个灯泡被打开了  cnt ++
        }
        
        if(cnt == m) res ++ ;  // 如果所有灯泡都打开了  res ++
    }
    
    cout << res << "\n" ;
    
    return 0;
}

D - 珠宝

题意
你的朋友送了你一个序列 D 你的朋友送了你一个序列D 你的朋友送了你一个序列D
这个序列 D 上一共有 n 个珠宝 这个序列D上一共有n个珠宝 这个序列D上一共有n个珠宝
按照从左到右的顺序 按照从左到右的顺序 按照从左到右的顺序
每个珠宝都有它的价值 w [ i ] 每个珠宝都有它的价值w[i] 每个珠宝都有它的价值w[i]

1 < = n < = 50 1 <= n <= 50 1<=n<=50
− 1 e 7 < = w [ i ] < = 1 e 7 -1e7 <= w[i] <= 1e7 1e7<=w[i]<=1e7

你可以对这些珠宝进行最多 k 次操作 你可以对这些珠宝进行 最多 k次操作 你可以对这些珠宝进行最多k次操作
1 < = k < = 100 1 <= k <= 100 1<=k<=100

每次操作你都可以 每次操作你都可以 每次操作你都可以
操作 A :取出 D 中最左边的宝石,放在手中。当 D 为空时,不能执行此操作。 操作A:取出D中最左边的宝石,放在手中。当D为空时,不能执行此操作。 操作A:取出D中最左边的宝石,放在手中。当D为空时,不能执行此操作。
操作 B :取出 D 中最右边的宝石,放在手中。当 D 为空时,不能执行此操作。 操作B:取出D中最右边的宝石,放在手中。当D为空时,不能执行此操作。 操作B:取出D中最右边的宝石,放在手中。当D为空时,不能执行此操作。
操作 C :选择手中的任意一个宝石并将其插入 D 的左端。如果手中没有宝 操作C:选择手中的任意一个宝石并将其插入D的左端。如果手中没有宝 操作C:选择手中的任意一个宝石并将其插入D的左端。如果手中没有宝 石,则无法执行此操作。 石,则无法执行此操作。 石,则无法执行此操作。
操作 D :选择手中的任意一个宝石并将其插入 D 的右端。如果手中没有宝 操作D:选择手中的任意一个宝石并将其插入D的右端。如果手中没有宝 操作D:选择手中的任意一个宝石并将其插入D的右端。如果手中没有宝 石,则无法执行此操作。 石,则无法执行此操作。 石,则无法执行此操作。

求执行完最多 k 次操作之后你手上有的珠宝的价值之和的最大值 求执行完 最多k次操作之后 你手上有的珠宝的价值之和的最大值 求执行完最多k次操作之后你手上有的珠宝的价值之和的最大值
思路
注意到 n 的最大范围只有 50 , k 最大只有 100 注意到n的最大范围只有50,k最大只有100 注意到n的最大范围只有50k最大只有100
那么可以枚举左边选了多少个 , 右边选了多少个 那么可以枚举左边选了多少个,右边选了多少个 那么可以枚举左边选了多少个,右边选了多少个

如果选的当中有负的价值 , 肯定要把最小的从手中删除 如果选的当中有负的价值,肯定要把最小的从手中删除 如果选的当中有负的价值,肯定要把最小的从手中删除
( 负数越小绝对值越大 ) (负数越小绝对值越大) (负数越小绝对值越大)
前提是还有操作数可以用 前提是还有操作数可以用 前提是还有操作数可以用

如果手上的都是正价值 , 可以不用继续操作 如果手上的都是正价值,可以不用继续操作 如果手上的都是正价值,可以不用继续操作
更新一下当前答案即可 更新一下当前答案即可 更新一下当前答案即可
时间复杂度 O n 3 l o g n On^3logn On3logn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e6 + 10 , M = 2010 , mod = 1e9 + 7 ;

int n , k ;
int a[N] ;

signed main()
{
    cin >> n >> k ;
    
    fer(i,1,n) sf(a[i]) ;
    
    int res = 0 ;  // 答案
    
    for(int i = 0 ; i <= n ; i ++)  //  左边选i个
    {
        for(int j = 0 ; j <= n ; j ++)  // 右边选j个
        {
            if(i + j > k) continue ;  // 如果操作数大于k了 就退出
            
            multiset<int> q ;  // 包含重复元素的set(set内元素从小到大自动排序)
            int s = 0 ;  // 这种选法的最大值
            
            // 左边选i个
            for(int l = 1 ; l <= i ; l ++)
            {
                q.insert(a[l]) ; 
                s += a[l] ;
            }
            
            // 右边选j个
            for(int r = n ; r >= n - j + 1 && r > i ; r --)
            {
                q.insert(a[r]) ;
                s += a[r] ;
            }
            
            if(i + j < k)  // 如果操作数不够k个 , 剩下的操作用来删除  
            {
                int d = k - (i + j) ;  // 可以用来删除的操作数
                while(d)  // 只要 d > 0 就可以删
                {
                    if(*q.begin() >= 0) break ;  // 如果最小的元素大于等于0  说明不用删除
                    if(*q.begin() < 0)   //  反之删除
                    {
                        s -= *q.begin() ; 
                        q.erase(q.begin()) ;
                    }
                    d -- ;  
                }
            }
            
            res = max(res,s) ;  // 更新答案
        }
    }
    
    cout << res << "\n" ;
    return 0;
}

E - 道路工程

题意
有一条从西到东的无限长的街道,我们认为这是一条无限长的数轴 有一条从西到东的无限长的街道,我们认为这是一条无限长的数轴 有一条从西到东的无限长的街道,我们认为这是一条无限长的数轴
这条街上计划进行 N 项道路工程。 这条街上计划进行N项道路工程。 这条街上计划进行N项道路工程。
第 i 个道路工程在坐标 X i 处阻段该点阻挡的时间从 S i − 0.5 到 T i − 0.5 第i个道路工程在坐标Xi处阻段该点 阻挡的时间从Si - 0.5 到 Ti - 0.5 i个道路工程在坐标Xi处阻段该点阻挡的时间从Si0.5Ti0.5
也就是说在 S i − 0.5 到 T i − 0.5 这段时间内 X i 这个点不能通过 也就是说在 Si - 0.5 到 Ti - 0.5 这段时间内 Xi 这个点不能通过 也就是说在Si0.5Ti0.5这段时间内Xi这个点不能通过
第 i 个人将在时间 D i 开始从坐标 0 , 不断以速度 1 个单位每秒正向行走,到达阻段点时停止行走。 第i个人将在时间Di开始从坐标0 , 不断以速度1个单位每秒正向行走,到达阻段点时停止行走。 i个人将在时间Di开始从坐标0,不断以速度1个单位每秒正向行走,到达阻段点时停止行走。
求出每个人将要走的距离 求出每个人将要走的距离 求出每个人将要走的距离
如果这个人走的过程中不会遇到阻断点 如果这个人走的过程中不会遇到阻断点 如果这个人走的过程中不会遇到阻断点
则输出 − 1 则输出-1 则输出1
1 < = N , Q < = 2 e 5 ( 200000 ) 1 <= N , Q <= 2e5 (200000) 1<=N,Q<=2e5(200000)
0 < = S i < T i < = 1 e 9 ( 1000000000 ) 0 <= Si < Ti <= 1e9 (1000000000) 0<=Si<Ti<=1e9(1000000000
1 < = X i < = 1 e 9 1 <= Xi <= 1e9 1<=Xi<=1e9
0 < = D 1 < D 2 < D 3 < . . . . . . . < D q < = 1 e 9 0 <= D1 < D2 < D3 < ....... < Dq <= 1e9 0<=D1<D2<D3<.......<Dq<=1e9
思路
其实我们可以发现每个人到达每个点的时间是动态的 其实我们可以发现每个人到达每个点的时间是动态的 其实我们可以发现每个人到达每个点的时间是动态的
枚举每个人去求行走距离显然不行 枚举每个人去求行走距离显然不行 枚举每个人去求行走距离显然不行

但是每个阻挡点的阻挡时间是不变的 但是每个阻挡点的阻挡时间是不变的 但是每个阻挡点的阻挡时间是不变的
考虑从这上面做 考虑从这上面做 考虑从这上面做

在 S i − 0.5 到 T i − 0.5 这段时间内 X i 这个点不能通过 在 Si - 0.5 到 Ti - 0.5 这段时间内 Xi 这个点不能通过 Si0.5Ti0.5这段时间内Xi这个点不能通过
等价于在区间 [ S i , T i − 1 ] 这段时间之内这个人必须不能经过这个点 等价于在区间[Si,Ti-1]这段时间之内这个人必须不能经过这个点 等价于在区间[Si,Ti1]这段时间之内这个人必须不能经过这个点
否则答案就是 X i 否则答案就是Xi 否则答案就是Xi

假设这个人从 0 号点的出发时间为 k 假设这个人从0号点的出发时间为k 假设这个人从0号点的出发时间为k
他到达 X i 这个点的时间为 X i + k 他到达Xi这个点的时间为Xi+k 他到达Xi这个点的时间为Xi+k
如果 S i < = X i + k < = T i − 1 如果Si <= Xi + k <= Ti-1 如果Si<=Xi+k<=Ti1
说明这个人最远只能到达 X i 这个点 说明这个人最远只能到达Xi这个点 说明这个人最远只能到达Xi这个点
把式子变形一下即为 把式子变形一下即为 把式子变形一下即为
S i − X i < = k < = T i − X i − 1 Si - Xi <= k <= Ti - Xi -1 SiXi<=k<=TiXi1

所以我们可以预处理所有的 [ S i − X i , T i − X i − 1 ] 这个区间 所以我们可以预处理所有的[Si-Xi,Ti-Xi-1]这个区间 所以我们可以预处理所有的[SiXi,TiXi1]这个区间
这个区间的权值为 X i 这个区间的权值为Xi 这个区间的权值为Xi

所以题目变成了 所以题目变成了 所以题目变成了
给你 n 个区间 , 每个区间都有一个权值 给你n个区间,每个区间都有一个权值 给你n个区间,每个区间都有一个权值
在给你 q 个询问 在给你q个询问 在给你q个询问
每个询问给你一个数 每个询问给你一个数 每个询问给你一个数
求这个数所在区间的最小权值 求这个数所在区间的最小权值 求这个数所在区间的最小权值

其实很明显是一个区间修改 + 单点最小值查询 其实很明显是一个区间修改+单点最小值查询 其实很明显是一个区间修改+单点最小值查询
可以裸线段树 / 平衡树 + 离散化 O n l o g n 可以裸线段树/平衡树+离散化Onlogn 可以裸线段树/平衡树+离散化Onlogn
代码长度可能至少 100 行这里不太推荐 代码长度可能至少100行 这里不太推荐 代码长度可能至少100行这里不太推荐

这里推荐另外一种方法 这里推荐另外一种方法 这里推荐另外一种方法
二分 + s e t 动态维护 二分+set动态维护 二分+set动态维护

对区间按照权值大小从小到大排序 对区间按照权值大小从小到大排序 对区间按照权值大小从小到大排序
二分找到这个区间所有的人 二分找到这个区间所有的人 二分找到这个区间所有的人
更新这些人的答案在把这些人删掉 更新这些人的答案在把这些人删掉 更新这些人的答案在把这些人删掉
每个人最多只被遍历一次 每个人最多只被遍历一次 每个人最多只被遍历一次
可以做到时间复杂度为 n l o g n 可以做到时间复杂度为nlogn 可以做到时间复杂度为nlogn
时间复杂度 O n l o g n Onlogn Onlogn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e6 + 10 , M = 2010 , mod = 1e9 + 7 ;

int n , t ;  //  n个阻断工程和t个人
struct ai{
    int l , r , x ;
}q[N] ;    //  保存区间及其权值
set<pll> se ;   // set里面放2个数 {到达0号点的的时间,编号}
int ans[N] ;  // 答案数组

bool cmp(ai a , ai b)
{
    return a.x < b.x ;
}
// 按照Xi的权值大小从小到大排序

signed main()
{
    cin >> n >> t ;
    
    fer(i,1,n)
    {
        int l , r , x ;
        sf(l) , sf(r) , sf(x) ;
        q[i] = {l-x,r-x-1,x} ; 
    }
    
    sort(q + 1 , q + 1 + n , cmp) ;
    
    fer(i,1,t)
    {
        int x ;
        sf(x) ;
        se.insert({x,i}) ;  // {这个人到达0号点的的时间,编号}
    }
    
    fer(i,1,t) ans[i] = -1 ;
    
    fer(i,1,n)
    {
        int l = q[i].l , r = q[i].r , x = q[i].x ;
        
        while(se.size())  // 只要set里面还有数
        {
            auto it = se.lower_bound({l,-2e9}) ;
            // 找到第一个大于等于l这个数
            
            if(it == se.end()) break ;  // 如果它不存在 就退出
            // stl的所有容器end() 指向一个不存在的数  自动处理边界问题
            if(it -> first > r) break ; //  如果这个数不在[l,r]这个区间之内,也退出 
            
            ans[it -> second] = x ;  // it -> second 表示这个人的编号
            se.erase(it) ;  // 删除
            // set的操作基于平衡树 单次操作logn
        }
    }
   	// 边遍历边删除 每个数只被遍历一次 故时间复杂度为nlogn
   	
    fer(i,1,t) cout << ans[i] << "\n" ;
    
    return 0;
}

F - 蛙跳

题意
有一只青蛙,有 0 , 1 , ⋯ , N − 1 个荷叶。每个荷叶上有权值 s i 。 有一只青蛙,有0,1,⋯,N−1个荷叶。每个荷叶上有权值si。 有一只青蛙,有0,1,,N1个荷叶。每个荷叶上有权值si
你现在在 0 这个荷叶上 你现在在0这个荷叶上 你现在在0这个荷叶上

操作 1 操作1 操作1
选定任意的 2 个正整数 A , B ,初始分数为 0 。 选定任意的2个正整数A, B,初始分数为0。 选定任意的2个正整数A,B,初始分数为0

假设当前位置为 x : 假设当前位置为x: 假设当前位置为x

操作 2 操作2 操作2
你的坐标移动到 x + A 这个位置上,并且 y = x + A 你的坐标移动到 x + A 这个位置上 , 并且y = x + A 你的坐标移动到x+A这个位置上,并且y=x+A
如果 y = N − 1 ,游戏结束。 如果y=N−1,游戏结束。 如果y=N1,游戏结束。
如果 y ≠ N − 1 ,但是 y 这个荷叶存在,那么分数增加 s i ,并且这片荷叶消失。 如果y≠N−1,但是y这个荷叶存在,那么分数增加si,并且这片荷叶消失。 如果y=N1,但是y这个荷叶存在,那么分数增加si,并且这片荷叶消失。
如果 y ≠ N − 1 ,但是 y 这个荷叶不存在,那么分数减去 10 的 100 次方,游戏结 如果y≠N−1,但是y这个荷叶不存在,那么分数减去10的100次方,游戏结 如果y=N1,但是y这个荷叶不存在,那么分数减去10100次方,游戏结 束。 束。 束。

操作 3 操作3 操作3
你的坐标移动到 x − B 这个位置上,并且 y = x − B 你的坐标移动到 x - B 这个位置上 , 并且y = x - B 你的坐标移动到xB这个位置上,并且y=xB
如果 y = N − 1 ,游戏结束。 如果y=N−1,游戏结束。 如果y=N1,游戏结束。
如果 y ≠ N − 1 ,但是 y 这个荷叶存在,那么分数增加 s i ,并且这片荷叶消失。 如果y≠N−1,但是y这个荷叶存在,那么分数增加si,并且这片荷叶消失。 如果y=N1,但是y这个荷叶存在,那么分数增加si,并且这片荷叶消失。
如果 y ≠ N − 1 ,但是 y 这个荷叶不存在,那么分数减去 10 的 100 次方,游戏结 如果y≠N−1,但是y这个荷叶不存在,那么分数减去10的100次方,游戏结 如果y=N1,但是y这个荷叶不存在,那么分数减去10100次方,游戏结 束。 束。 束。

然后不断重复 2 和 3 操作 然后不断重复2和3操作 然后不断重复23操作

问选定最优的 A 、 B 的情况下,得到的最高分数为多少? 问选定最优的A、B的情况下,得到的最高分数为多少? 问选定最优的AB的情况下,得到的最高分数为多少?
1 < = n < = 1 e 5 ( 100000 ) 1 <= n <= 1e5(100000) 1<=n<=1e5(100000)
− 1 e 9 < = s i < = 1 e 9 ( 1 后面 9 个 0 ) -1e9 <= si <= 1e9 (1后面9个0) 1e9<=si<=1e9(1后面90
s 0 = s n − 1 = 0 s0 = sn-1 = 0 s0=sn1=0
思路
考虑,选定了 A 、 B 后,青蛙的行走路线为: 考虑,选定了A、B后,青蛙的行走路线为: 考虑,选定了AB后,青蛙的行走路线为:
0 , A , A − B , A + ( A − B ) , 2 ( A − B ) , ⋯ , K ( A − B ) , A + K ( A − B ) 0,A,A−B,A+(A−B),2(A−B),⋯,K(A−B),A+K(A−B) 0,A,AB,A+(AB),2(AB),,K(AB),A+K(AB)

令 C = A − B : 令C=A−B: C=AB
0 , A , C , A + C , 2 C , ⋯ , K C , A + K C 0,A,C,A+C,2C,⋯,KC,A+KC 0,A,C,A+C,2C,,KC,A+KC
显然有: A + K C = N − 1 : 显然有:A+KC=N−1: 显然有:A+KC=N1

将 A = N − 1 − K C 带入上式 将A = N - 1 - KC带入上式 A=N1KC带入上式
0 , N − 1 − K C , C , N − 1 − ( K − 1 ) C , 2 C , ⋯ , K C , N − 1 0,N−1−KC,C,N−1−(K−1)C,2C,⋯,KC,N−1 0,N1KC,C,N1(K1)C,2C,,KC,N1

那么当 K 、 C 确定的时候,行走路线就已经确定。 那么当K、C确定的时候,行走路线就已经确定。 那么当KC确定的时候,行走路线就已经确定。
并且有一个限制条件为 K C < N ,那么显然枚举 K 、 C 是 O ( n l o g n ) 的。 并且有一个限制条件为KC<N,那么显然枚举K、C是O(nlogn)的。 并且有一个限制条件为KC<N,那么显然枚举KCO(nlogn)的。

并且我们发现,当我们固定 C ,递增 K 的时候,行走路线的变化是这样的: 并且我们发现,当我们固定C,递增K的时候,行走路线的变化是这样的: 并且我们发现,当我们固定C,递增K的时候,行走路线的变化是这样的:
0 , N − 1 0,N−1 0,N1
0 , N − 1 − C , C , N − 1 0,N−1−C,C,N−1 0,N1C,C,N1
0 , N − 1 − 2 C , C , n − 1 − C , 2 C , N − 1 0,N−1−2C,C,n−1−C,2C,N−1 0,N12C,C,n1C,2C,N1

每次增加的是 N − 1 − K C 和 K C ,这两个点,只需要加上就好了 每次增加的是N−1−KC和KC,这两个点,只需要加上就好了 每次增加的是N1KCKC,这两个点,只需要加上就好了
并且要注意判断是否走到重复的点上了。 并且要注意判断是否走到重复的点上了。 并且要注意判断是否走到重复的点上了。
时间复杂度 O n l o g n Onlogn Onlogn

#include <bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define der(i,a,b) for(re i = a ; i >= b ; -- i)
#define de(x) cout << x << "\n" 
#define sf(x) scanf("%lld",&x)
#define pll pair<int,int> 
#define re register int
#define int long long 
#define pb push_back
#define y second 
#define x first 
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e5 + 10 , M = 2010 , mod = 1e9 + 7 ;

int n ;
int s[N] ;
int used[N] ;

signed main()
{
    cin >> n ;
    
    fer(i,0,n-1) sf(s[i]) ;
    
    int res = 0 ;
    
    for(int c = 1 ; c <= n ; c ++)
    {
        int ans = 0 ;
        for(int k = 1 ; k * c < n ; k ++)
        {
            int a = k * c;
			int b = n - 1 - k * c;
			int A = b, B = b - c;
			
			if (A <= 0 || B <= 0) break; 
			if (a < 0 || a >= n || b < 0 || b >= n || a == b) break; 
			
			if (used[a] == c || used[b] == c) 
			{
				break;
			}
			
			used[a] = c;
			used[b] = c;
			
			ans += s[a];
			ans += s[b];
			res = max(res, ans);
        }
    }
    
    cout << res << "\n" ;
    
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值