题目链接 - Codeforces Round 933 (Div. 3)
博客中的代码仅供参考,请勿直接提交源代码,这对提升实力毫无作用!!!
题目写一半,自己不小心把编译器搞坏了,中途又重新下载mingw呜呜呜,好在这场时间长
A. Rudolf and the Ticket
题意
有两个数组a和b,长度分别为n,m。即 a 1 , a 2 , . . . , a n , b 1 , b 2 , . . , b m a_1,a_2,...,a_n,b_1,b_2,..,b_m a1,a2,...,an,b1,b2,..,bm 。
现要求你从两个数组中各选择一个数 a i , b j a_i,b_j ai,bj 并且要求 a i + b j ≤ k a_i + b_j \le k ai+bj≤k ,问有多少种选法。
思路
二分查找, 对b数组排序,然后枚举 a i a_i ai , 使用upperbound来在b中查找第一个大于 k − a i k - a_i k−ai 的下标p,那么b数组中的区间 [ 0 , p − 1 ] [0,p-1] [0,p−1]上的数都满足和 a i a_i ai 相加之后小于等于 k k k。 于是产生了 p p p的贡献。按此理计算出总贡献即可。复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n,m,k;
cin>>n>>m>>k;
vector<int> a,b;
rep(i,1,n){
int num;cin>>num; a.push_back(num);
}
rep(i,1,m){
int num;cin>>num; b.push_back(num);
}
int sum = 0;
sort(b.begin(),b.end());
for(auto p : a){
int t = k - p;
sum += upper_bound(b.begin(),b.end(),t) - b.begin();
}
cout<<sum<<endl;
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}
B - Rudolf and 121
题意
现在有一个长度为 n n n的数组 a a a, a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 要求你使用任意次如下操作,问能否使得数组a全为0 。
选择一个下标 i ( 2 ≤ i ≤ n − 1 ) i(2 \le i \le n-1) i(2≤i≤n−1),然后进行下面三步:
- 令 a i − 1 − = 1 a_{i-1} -= 1 ai−1−=1
- 令 a i − = 2 a_{i} -= 2 ai−=2
- 令 a i + 1 − = 1 a_{i+1} -= 1 ai+1−=1
思路
贪心,观察操作可以发现,我们若想让 a 1 a_1 a1 归零, 那么就必须选择 i = 2 i = 2 i=2 进行操作,并且要准确地操作 a 1 a_1 a1 次。 此时 a 1 a_1 a1就一定变为了0 。更新数组,即更新 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3 。
然后我们就按照此法再令 i = 3 i = 3 i=3 ,进行 a 2 a_2 a2次操作,来试图将 a 2 a_2 a2 归零。 但我们发现如果某个数变成了负数,那么他永远无法变成0,因此我们就可以输出NO并结束这组样例。
如此法遍历整个数组即可。
在遍历完整个数组后,我们还无法得知 a n − 1 , a n a_{n-1}, a_n an−1,an 是否成功清零,判断一下即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n;
cin>>n;
vector<int> vec(n+5);
rep(i,1,n) cin>>vec[i];
rep(i,2,n-1){
if(vec[i-1]<0||vec[i]<0||vec[i+1]<0){cout<<"No\n";return ;}//如果某个数变成了负数
if(vec[i-1] == 0) continue;
int t = vec[i-1];//要进行的操作次数
vec[i] -= 2 * t;
vec[i+1] -= t;
vec[i-1] -= t;
}
if(vec[n-1] != 0 || vec[n]!=0){cout<<"No\n";return ;}//如果这两个数没有成功清零
cout<<"Yes\n";
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}
C - Rudolf and the Ugly String
题意
如果一个字符串中存在子串map
或者 pie
,那么就说这个子串是丑陋的,否则就说它是美丽的。现在给你一个字符串,问最少需要删除几个字符才能使得字符串是美丽的。
思路
我们需要拆散这个字符串中的map
和 pie
, 容易发现,如果存在某个子串是map
,那么我们删除中间的a即可,这样能够删除这里的map
,并且保证不会出现新的map
,pie
也是同理, 因此我们找这个字符串中存在几个map或者pie子串即可(吗?)
如果仅仅想到这一步,那么你将获得一发罚时。因为如果字符串中存在子串mapie
,那么我们照此法将会删除a和i两个字符,然而直接删除p字符就可以同时拆散map
和pie
。
所以最终答案应为字符串中map与pie的数量相加再减去mapie的数量
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n;
cin>>n;
string str;
cin>>str;
int ans = 0;
for(int i = 0;i<n-2;i++){
if(i<=n-5 && str.substr(i,5) == "mapie"){ans--;} //每出现一个mapie就让答案减一
if(str.substr(i,3) == "map") ans++;
if(str.substr(i,3) == "pie") ans++;
}
cout<<ans<<endl;
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}
D. Rudolf and the Ball Game
题意
有n个人按顺时针顺序s围成一个环,标号分别为 1 , 2 , . . , n 1,2,..,n 1,2,..,n , 如图:
起初球在第x个人的手中,下面进行m次传球。每次传球遵循如下格式:r c
其中r为一个整数,表示传球的距离。c为0 1 ?
中的一个字符 , 0
表示下一步要顺时针传球, 1
表示这次传球是逆时针的,?
则表示这次传球不确定是顺时针还是逆时针。如上图中2号人顺时针传距离5时,传给了7,逆时针时则传给了4 。
问进行完m次传球后, 哪些人可能拿到球。
思路
dp,我们使用一个数组 d p dp dp记录上一步都哪些人可能获得球后,起初 d p [ x ] = 1 dp[x] = 1 dp[x]=1 ,其他值为0 。
对于每一步传球操作,根据操作情况来生成一个新的数组 f f f表示进行完这步操作后哪些人可能会获得球。然后将 f f f内容复制给 d p dp dp之后, 清空 f f f 。不断重复如此即可得到最终的数组。
对标号 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 的人进行循环取模操作会比较复杂,可以考虑令所有人的标号减一,最终输出的时候通过加一来恢复原始标号。
代码
还有一种存储方式是用set, 但原理相同,这里不在赘述
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n,m,x;
cin>>n>>m>>x;
//这里的vector就等价于数组,但是支持直接赋值整个数组
vector<bool> dp(n+5);//老数组
vector<bool> f(n+5);//新数组
dp[x-1] = 1; //做了减一操作,更方便取模
rep(i,1,m){
int k;char op;
cin>>k>>op;
for(int i =0;i<n;i++){
if(dp[i]){
if(op !='0') f[(i-k+n)%n] = 1; //1和?会进行逆时针操作
if(op !='1') f[(i+k)%n] = 1;//0和?会进行顺时针操作
//算是一个小技巧,可以少写一个判断
}
}
dp = f;//令dp等于f
fill(f.begin(),f.end(),0);//清空f
}
vector<int> ans;//记录哪些人可能获得球
rep(i,0,n-1) if(dp[i]) ans.push_back(i+1);
cout<<ans.size()<<'\n';
for(auto p : ans){
cout<<p<<" ";
}
puts("");
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}
E. Rudolf and k Bridges
题意
现在有n行m列的网格a, a i , j a_{i,j} ai,j 表示第 i i i行第 j j j 列的海水深度 , 保证第1列和第m列是大陆, 深度为0。
对每行你可以按照如下规则建桥:
在第 1 1 1列和第 m m m列必须要有支柱。然后你可能还需要在海中树立一些柱子。需要保证不存在任何两个相邻的柱子之间的距离大于d,如果两个柱子的距离分别为 i , j i,j i,j 那么他们之间的距离是 j − i − 1 j - i - 1 j−i−1 。
建造一个桥所消耗的费用为 这座桥上所有柱子所在的海水深度加一 的和。
现在需要你建造k个连续的桥。即选择一个 i , i ≤ n − k + 1 i,i\le n-k+1 i,i≤n−k+1,在 i , i + 1 , . . . , i + k − 1 i,i+1,...,i+k-1 i,i+1,...,i+k−1 行都建一个桥。问最少需要多少费用。
思路
比较容易得知,对于建造不同的桥,他们的费用是互不影响的。因此我们可以分别计算这n座桥的建造费用,然后选择区间和最小的那组即可。
我们先讲在找到每行的建桥费用并存入ans数组后, 如何找这k个连续的桥(如果你已经会了就跳过)
我们对ans数组进行计算前缀和 ,存入sum中, 于是 s u m [ i ] − s u m [ i − k ] sum[i] - sum[i-k] sum[i]−sum[i−k] 则表示了从第 i − k + 1 i-k+1 i−k+1 开始建k座桥需要的总花费。于是枚举每一个可能的i,令 r e s res res为所有可能中的最小值即可。
下面来探讨如何计算建造某个桥的费用。
我们可以使用dp的思想, d p [ i ] dp[i] dp[i] 表示在第 i i i列立下一根柱子的情况下,建好前 i i i格最少需要多少花费,这样 d p [ m ] dp[m] dp[m] 就是建造这整座桥的花费。我们 d p [ i ] dp[i] dp[i] 必须由 d p [ i − 1 ] , d p [ i − 2 ] , . . . , d p [ i − k − 1 ] dp[i-1],dp[i-2],...,dp[i-k-1] dp[i−1],dp[i−2],...,dp[i−k−1] 中的某一个转移而来,可以发现,在建造完成第 i i i格的柱子后,不论之前的方案如何,对之后的影响都是相同的。于是我们选择这些元素中值最小的作为母状态即可。
但是如果我们直接枚举 d p [ i − 1 ] , d p [ i − 2 ] , . . . , d p [ i − k − 1 ] dp[i-1],dp[i-2],...,dp[i-k-1] dp[i−1],dp[i−2],...,dp[i−k−1] 这些内容,那么计算一个桥的复杂度就是 O ( m ∗ d ) O(m*d) O(m∗d) 这显然会导致超时。于是我们考虑使用数据结构来加速。
下面介绍方案一:优先队列
我们使用优先队列存储一个结构体,表示柱子的下标 x x x以及 d p [ x ] dp[x] dp[x] , 并且规定队头元素是 d p [ x ] dp[x] dp[x]最小的那个,这样我们就保证可以在O(1)的时间内选出值最小的那个元素。 同时我们每次使用队头元素前都要检查队头元素是否过期(若队头元素的下标 x x x与当前下标 i i i 的距离超过了d ,那么它就已经过期,在之后都无法使用),如果过期,就直接出队。
通过上面的构造,我们的优先队列就满足了如下两个性质:
- 队头元素一定是花费最小的那个。
- 队头元素一定没有过期
于是我们就可以借此来知晓 d p [ i ] dp[i] dp[i]的值,即 d p [ i ] = p q . f r o n t ( ) . w a s + d e p + 1 dp[i] = pq.front().was + dep+1 dp[i]=pq.front().was+dep+1 。 其中 w a s was was表示花费, d e p dep dep表示第 i i i格的深度。之后将 ( i , d p [ i ] ) (i,dp[i]) (i,dp[i])入队。 复杂度为 O ( m l o g m ) O(mlogm) O(mlogm)
方案二: 单调队列
很常用的一个数据结构。我们使用一个双端队列,并维护这个队列是递增的。和上面的优先队列原理类似。我们保证队头元素一定不是过期的,如果已经过期就出队它。(若下标 x x x和 i i i的距离大于 d d d,那么我们说 x x x已经过期了),同时在入队时,我们保证入队的这个元素一定比队尾元素更大,如果违背了这个规则,我们就不断从队尾出队(这是一个双端队列,所以可以从队尾出队),等到不违背这个规则时,再入队元素。
于是我们的队列就满足了如下两个性质:
- 队列中的所有元素从队头到队尾单调递增,因此队头元素一定最小
- 队列中队头元素一定不是过期的。
于是我们就可以保证得到正确的 d p [ i ] dp[i] dp[i] ,于是就可以得到一个桥的花费,即 d p [ m ] dp[m] dp[m]
代码
优先队列:
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
struct Node
{
int x; //下标x
int wast;//花费waste
friend bool operator<(Node n1,Node n2){ //用来决定优先队列的排序顺序
return n1.wast > n2.wast;
}
};
void solve(){
int n,m,k,d;
cin>>n>>m>>k>>d;
vector<int> ans;
ans.push_back(0);
int t = n;//n还有用
while(t--){//计算这n行各自的花费
priority_queue<Node> pq;
int x;cin>>x;pq.push({1,1});//第一个一定是0,所以直接入队{1,1}
for(int i = 2;i<=m;i++){
cin>>x;
while(pq.top().x+d+1<i){//不断出队过期元素
pq.pop();
}
if(i == m){//如果到达了河对岸
ans.push_back(pq.top().wast+x+1);//可以存答案了
break;
}
pq.push({i,pq.top().wast+x+1});//入队元素
}
}
for(int i = 1;i<=n;i++){
ans[i] += ans[i-1];
}
int res = INF;
for(int i =k;i<=n;i++){
res = min(res,ans[i] - ans[i-k]);
}
cout<<res<<endl;
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}
单调队列(代码来自队友):
这是队友CSDN主页,超强!
#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <queue>
#define mk make_pair
using namespace std;
typedef long long ll;
const int maxn=105;
const int maxm=2e5+5;
const ll inf=1e18;
int T,n,m,k,d;
int a[maxm];
ll dp[maxm];
ll cost[maxn];
int main(){
cin>>T;
while(T--){
cin>>n>>m>>k>>d;
for(int bridge=1;bridge<=n;bridge++){
for(int i=1;i<=m;i++)cin>>a[i],dp[i]=inf; //初始化dp为全INF(最大值)
deque<pair<ll,ll> > q;
dp[1]=1;
q.push_back(mk(1,1));
for(int i=2;i<=m;i++){
dp[i]=min(dp[i],q.front().first+a[i]+1);
while(!q.empty() && dp[i]<=q.back().first)q.pop_back();//保证队列单调性
q.push_back(mk(dp[i],i));
if(i-d>q.front().second)q.pop_front();//保证队头不过期
}
// cout<<dp[m]<<endl;
cost[bridge]=cost[bridge-1]+dp[m];//dp[m]是建这座桥的最少费用,cost直接计算了前缀和
}
ll ans=inf;
for(int i=k;i<=n;i++)
ans=min(ans,cost[i]-cost[i-k]);
cout<<ans<<endl;
}
return 0;
}
F - Rudolf and Imbalance
题意
现在给你三个数组 a , d , f a,d,f a,d,f , 长度分别为 n , m , k n,m,k n,m,k,你可以从 d d d和 f f f中各选出一个数 d i , f j d_i, f_j di,fj 然后把 d i + f j d_i +f_j di+fj 放入a中, 问进行最多一次 该操作后, a数组中的 M A X i = 1 n ′ − 1 ( a i + 1 − a i ) MAX_{i=1}^{n'-1}(a_{i+1} - a_i) MAXi=1n′−1(ai+1−ai) 最小是多少。 其中的 n ′ n' n′ 表示为进行至多一次操作之后的a的元素数量。
保证a数组是有序的。
思路
由于我们只能插入一个数,因此只有插入到最大的那个间隙中,才能使得最大间隔变短。 因此我们可以先求出最大间隔 m x mx mx和次大间隔 s e m x semx semx , 并找到产生最大间隔的那两个数 l l l和 r r r,即 r − l = m x r - l = mx r−l=mx 。 假设我们找到了一个数 t t t,来插入到 l l l和 r r r中间,那么整个数组a的最大间隔就 变为了 M A X ( s e m x , a b s ( t − l ) , a b s ( r − t ) ) MAX(semx,abs(t-l),abs(r-t)) MAX(semx,abs(t−l),abs(r−t)) , 我们设 m i d = l + r 2 mid = \frac{l+r}{2} mid=2l+r ,那么很明显,semx是一个定值,因此我们的t越靠近mid,便越可能得到更小的答案。于是整道题的目标就变为了找到最接近$mid 的那个 的那个 的那个t$ 。
我们对数组
f
f
f进行排序, 然后枚举
d
d
d中 的元素
x
x
x, 使用lower_bound
来从
f
f
f中找到最接近
m
i
d
−
x
mid - x
mid−x 的两个值作为
t
t
t的 待选项,借此更新更优的
t
t
t ,得到更优的
a
n
s
ans
ans 。最终我们再和次大间隔
s
e
m
x
semx
semx取
m
a
x
max
max即可。
需要注意的是,
- 注意
lower _bound
之后可能找到的是数组边界,此时可能会不存在其中一个待选项 t t t。 - 要特殊考虑n == 2 时没有次大值 s e m x semx semx的情况。
代码
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long
#define rep(i,l,r) for(int i = l;i<=r;i++)
#define per(i,r,l) for(int i = r;i>=l;i--)
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int,int> PII;
void solve(){
int n ,m,k;
cin>>n>>m>>k;
vector<int> a(n+5);//数组a
for(int i = 1;i<=n;i++) cin>>a[i];
int mx = 0,semx = 0;//a中相邻两个数的差,最大和次大
int l,r;//最大差的左项和右项
//初始化mx和semx,l,r
if(n>=3){
if(a[3]-a[2]>a[2]-a[1]){
l = a[2],r = a[3];
mx = a[3]-a[2];
semx =a[2]-a[1];
}else {
l = a[1],r = a[2];
mx = a[2]-a[1];
semx =a[3]-a[2];
}
}else if(n == 2){
mx = a[2]-a[1];l = a[1];r = a[2];
}
//找mx和semx,l,r
rep(i,3,n-1){
if(a[i+1]-a[i]>mx){
semx = r - l;
mx = a[i+1]-a[i];
r = a[i+1],l = a[i];
}else if(a[i+1]-a[i]>semx){
semx = a[i+1]-a[i];
}
}
int mid = (l+r)/2;
vector<int> d,f;
rep(i,1,m){int num;cin>>num;d.push_back(num);}
rep(i,1,k){int num;cin>>num;f.push_back(num);}
sort(f.begin(),f.end());
int ans = INF;
for(auto x : d){//枚举d
int p = upper_bound(f.begin(),f.end(),mid-x) - f.begin();
if(p!=k){
int t = f[p]+x;
int newans = max(abs(l-t),abs(t-r));
ans = min(ans,newans);
}if(p!=0){
int t = f[p-1]+x;
int newans = max(abs(l-t),abs(t-r));
ans = min(ans,newans);
}
}
if(ans > mx){cout<<mx<<endl;return;}
cout<<max(semx,ans)<<endl;
}
signed main(){
int T = 1;
cin>>T;
while(T--){
solve();
}
return 0;
}