Codeforces Round #515 (Div. 3) 题解
文章目录
A. Vova and Train
-
题意
给出区间 [ 1 , n ] [1,n] [1,n],其中你想访问坐标为 v v v的倍数的点,而 [ l , r ] [l,r] [l,r]区间是访问不了的,问你能访问多少个 v v v的倍数的点。 -
解题思路
先解决这样一个问题, [ 1 , n ] [1,n] [1,n]区间中有多少点为 v v v的倍数,易得为 n / v n/v n/v。那么如果我们知道了 [ l , r ] [l,r] [l,r]中有多少这样的点,那么两者相减自然可得。
由于区间 [ l , r ] [l,r] [l,r]存在特殊性,我们这样考虑,即先求出 [ 1 , r ] [1,r] [1,r]和 [ 1 , l − 1 ] [1,l-1] [1,l−1]存在符合要求的点数,再两者相减即可得到。
故此题得解。 -
AC代码
/**
*@filename:A
*@author: pursuit
*@created: 2021-08-23 20:10
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t;
ll L,v,l,r;
void solve(){
ll cnt = L / v - (r / v - (l - 1) / v);
printf("%lld\n", cnt);
}
int main(){
cin >> t;
while(t -- ){
cin >> L >> v >> l >> r;
solve();
}
return 0;
}
B. Heaters
-
题意
给你 n n n个点,其中值为 1 1 1代表可以放置加热器,而当第 i i i个点放置加热器, [ i − r + 1 , i + r − 1 ] [i-r+1,i+r-1] [i−r+1,i+r−1]的点都能被加热到。问你需要放置多少加热器才能加热所有点。 -
解题思路
贪心的去考虑。我们用 l a s t last last来记录上一个放置加热器的位置,而 i i i则为这次需要处理的初始位置,按照题目要求,我们至少要加热到 i i i这个点,而其中能管辖到这个点的区间为 [ l a s t + 1 , i + r − 1 ] [last+1,i + r-1] [last+1,i+r−1],所以我们必须在这个区间放置一个加热器。
那么如果我们要放置,那么肯定是放置 [ l a s t + 1 , i + r − 1 ] [last+1,i + r-1] [last+1,i+r−1]这段区间最右边能放置加热器的位置,这样能使得我们覆盖的范围尽量往后移。
至此,我们只需要按照这样来贪心放置加热器即可。 -
AC代码
/**
*@filename:B
*@author: pursuit
*@created: 2021-08-23 20:24
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e3 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n,r,a[N];
void solve(){
//last为上次使用加热器的位置。
int last = 0, res = 0, i = 1, j, k;
while(i <= n){
j = i + r - 1, k = 0;
//j为管理i的最右边的点。我们需要在j以内找到一个加热器。
if(j > n)j = n;
while(j > last){
if(a[j]){
k = j;
break;
}
else{
-- j;
}
}
if(!k){
//说明没有找到加热器。
res = -1;
break;
}
else{
last = k, i = k + r;
++ res;
}
}
printf("%d\n", res);
}
int main(){
scanf("%d%d", &n, &r);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
solve();
return 0;
}
C. Books Queries
-
题意
有三种操作,第一种往最左边放书,第二种往最右边放书,第三种查询取出编号为 x x x的书的最小操作次数,只能从左边一个一个弹出书取出或者右边一个一个弹出书取出。 -
解题思路
这种我们很容易就想到双端队列。第一种操作和第二种操作都可以通过push_back(),push_front()实现。那么关键的一个问题就是怎么快速求出取出 x x x的最小操作次数呢?deque肯定是不行的,因为如果我们模拟这个过程,需要真正的弹出元素,时间复杂度巨大。
如果我们记录了每个点在双端队列中的位置以及双端队列的最左边的点和最右边的点,那么是不是可以 O ( 1 ) O(1) O(1)时间求出呢?
所以这道题关键在于我们需要用数组实现双端队列,然后开一个数组记录每个点在队列中的位置。
注意由于是双端队列,所以我们为了避免数组下标为负,所以我们需要偏移一个 N N N来避免负数下标。 -
AC代码
/**
*@filename:C
*@author: pursuit
*@created: 2021-08-23 20:52
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t,x,id[N];
char op[4];
//双端队列实现。
int q[N << 1],l,r;//实现一个偏移。
void solve(){
scanf("%s%d", op, &x);
if(op[0] == 'L'){
id[x] = l;
q[l --] = x;
}
else if(op[0] == 'R'){
id[x] = r;
q[r ++] = x;
}
else{
//cout << id[x] << " " << l << " " << r << endl;
printf("%d\n", min(id[x] - l - 1, r - id[x] - 1));
}
}
int main(){
scanf("%d", &t);
l = r = N;
++ r;
while(t -- ){
solve();
}
return 0;
}
D. Boxes Packing
-
题意
给你 m m m个盒子, n n n个物品,其中第 i i i个物品的体积为 a [ i ] a[i] a[i]。你从第 1 1 1个物品开始放,如果放完了所有的盒子都放不下了,就会移除第一个物品继续放,持续操作知道放完最后一个物品。问你最多能放多少个物品。 -
解题思路
根据题意,我们易得,最后放完的一定是后面连续的物品,所以我们只需要从后往前放知道知道放完所有的盒子且装不下了即可。此时放完的物品数就是最大的物品数了。
当然,我们也可以二分最开始放的盒子编号。然后判断是否可行,找到最优解即可。 -
AC代码
/**
*@filename:D
*@author: pursuit
*@created: 2021-08-23 21:06
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
//从集合中扔掉最左边的元素。说明右边的一定是连续装满的。
int n,m,k,a[N];
void solve(){
int cnt = 0, i;
for(i = n; i >= 1; -- i){
if(cnt + a[i] <= k){
cnt += a[i];
}
else{
cnt = a[i], -- m;
}
if(!m){
//如果没有盒子用了。
break;
}
}
printf("%d\n", n - i);
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
solve();
return 0;
}
E. Binary Numbers AND Sum
-
题意
给定两个二进制数 a , b a,b a,b,持续以下操作直到 b b b为 0 0 0。- 累加 a & b a\&b a&b
- 将 b b b右移一位,即 b = b > > 1 b=b>>1 b=b>>1。
-
解题思路
我们单纯模拟是不行的,因为 a , b a,b a,b的长度可达 2 e 5 2e5 2e5。
那么我们换种角度考虑,由于 a a a是不变的, b b b一直往右移,那么考虑 b b b上的 1 1 1的贡献,对于 b b b的第 i i i位的 1 1 1,它会贡献 a a a的前 i i i位的所有的 1 1 1,即与这些 1 1 1进行 & \& &操作,这样得到的即为 s u m [ i ] sum[i] sum[i],其中 s u m [ i ] sum[i] sum[i]表示 a a a的前 i i i位的十进制数值。
所以,我们可以先预处理出 s u m sum sum数组,然后累加 b b b上的所有 1 1 1的贡献即可。 -
AC代码
/**
*@filename:E
*@author: pursuit
*@created: 2021-08-23 21:31
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 998244353;
const int INF = 0x3f3f3f3f;
//考虑贡献,每个移动一次就相当于在a中贡献了一次。累加前缀和即可。
int n,m,sum[N];
char a[N],b[N];
int ksm(int n, int q){
int ans = 1;
while(q){
if(q & 1)ans = 1LL * ans * n % P;
q >>= 1;
n = 1LL * n * n % P;
}
return ans;
}
void solve(){
//我们需要从低位处理到高位。为了方便,所以我们需要反转字符串。
reverse(a + 1, a + 1 + n);
reverse(b + 1, b + 1 + m);
for(int i = 1; i <= n; ++ i){
if(a[i] == '1'){
sum[i] = ksm(2, i - 1);
}
}
for(int i = 1; i <= m; ++ i){
sum[i] = (sum[i] + sum[i - 1]) % P;
}
int res = 0;
for(int i = 1; i <= m; ++ i){
//考虑每一位的贡献。
if(b[i] == '1'){
res = (res + sum[i]) % P;
}
}
printf("%d\n", res);
}
int main(){
scanf("%d%d", &n, &m);
scanf("%s%s", a + 1, b + 1);
solve();
return 0;
}
F. Yet another 2D Walking
-
题意
给你 n n n个点,定义每个点 ( x i , y i ) (x_i,y_i) (xi,yi)属于 max ( x i , y i ) \max (x_i,y_i) max(xi,yi)级别。初始你处于 ( 0 , 0 ) (0,0) (0,0),即第 0 0 0个级别。规定你访问第 i i i级别的时候前 i − 1 i-1 i−1级别的所有点都要访问完。问访问完所有点的最短距离。 -
解题思路
我们需要清楚,在同一个 l e v e l level level中必须要访问所有的点。那么我们必然会有一个起始点和终止点,而从一个级别跳到另一个级别也是从这些点跳转的。所以我们要规划起始点和终止点。这里设置从上往下,从左往右。也可以按其他方式排列。但一定要保证在这个 l e v e l level level中的起始点和终止点一定是最外面的点。
我们可以用 d p [ i ] [ j ] dp[i][j] dp[i][j]来表示访问到第 i i i个 l e v e l level level, j j j点的最小距离。
其中 j j j为 0 0 0代表第一个点, j j j为 1 1 1代表最后一个点,即可以通过上一个 l e v e l level level的第一个点或者最后一个点得来。
根据以上分析,状态转移方程就很好列出了。
d p [ i ] [ 0 ] dp[i][0] dp[i][0]可以通过 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]和 d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i−1][1]得来,这个意思是从第 i − 1 i-1 i−1个 l e v e l level level的第一个点或者最后一个点跳到第 i i i个 l e v e l level level的最后一个点,再从最后一个点访问第一个点 (这样这个level中的其他的点在这个路径中也都放访问了) 。
其他状态同理可列举。 -
AC代码
/**
*@filename:F
*@author: pursuit
*@created: 2021-08-23 22:02
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n,m;
map<int,vector<pii> > levels;//存储每个级别的坐标点。
pii a,b,c,d;
ll dp[N][2];
bool cmp(pii a,pii b){
if(a.x == b.x){
return a.y > b.y;//列相同,按行排序。
}
return a.x < b.x;
}
int dist(pii a,pii b){
return abs(a.x - b.x) + abs(a.y - b.y);
}
void solve(){
levels[0].push_back({0,0});
for(auto &it : levels)sort(it.second.begin(), it.second.end(), cmp);
fill(dp[0], dp[0] + N * 2, 1e18);
dp[0][0] = dp[1][0] = 0;//初始化。
int cur = 0, pre = 0;
for(auto &it : levels){
++ cur;
//取出当前级别第一个点和最后一个点。
a = it.second[0], b = it.second.back();
//取出先前级别第一个点和最后一个点。
c = levels[pre][0], d = levels[pre].back();
dp[cur][0] = min(dp[cur][0], dp[cur - 1][0] +
dist(c,b) + dist(b,a));
dp[cur][0] = min(dp[cur][0], dp[cur - 1][1] +
dist(d,b) + dist(b,a));
dp[cur][1] = min(dp[cur][1], dp[cur - 1][0] +
dist(c,a) + dist(a,b));
dp[cur][1] = min(dp[cur][1], dp[cur - 1][1] +
dist(d,a) + dist(a,b));
pre = it.first;
}
printf("%lld\n", min(dp[cur][0], dp[cur][1]));
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d%d", &a.x, &a.y);
levels[max(a.x,a.y)].push_back(a);
}
solve();
return 0;
}

浙公网安备 33010602011771号