牛客小白月赛86
title: 牛客小白月赛86
categories:
- ICPC
tags:
- null
abbrlink: 9be380b5
date: 2024-07-17 00:00:00
B 水平考试
======
等价于两个集合 $S, T$ 判断 $S$ 中是否存在 $T$ 中所不包含的元素。
- 若存在则输出 0;
- 否则输出 10。
时间复杂度:$O(T)$
C题:区间查询当前区间可以被分为多少段,要求每段只有一种数字。
做法1:提前对所有段编号,查询时直接左右边界编号相减,注意边界需要特别处理
做法2:标记第i段与第i+1段之间的分界点,然后求前缀和,本质上和做法1一样
D题:给定网格,0表示障碍,1表示道路。问有多少块长方形道路?(正方形也是长方形)
对此我们首先用过bfs找到连通块,对于每个连通块我们需要维护这个连通块最大x,y坐标,
最小x,y坐标,以及连通块大小。当连通块是矩形的时候通过
统计矩形的(minX, minY) -> (maxX, maxY)
然后判断 (maxY - minY + 1) * (maxX - minX + 1) == 连通图的大小
// Problem: 剪纸游戏
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/73450/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
# define int long long
#define ull unsigned long long
#define pii pair<int,int>
#define baoliu(x, y) cout << fixed << setprecision(y) << x
#define endl "\n"
#define debug1(x) cerr<<x<<" "
#define debug2(x) cerr<<x<<endl
const int N = 1e3 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-8;
int n, m;
bool st[N][N];
int a[N][N];
int dx[5]={0,1,-1,0};
int dy[5]={1,0,0,-1};
vector<pii>mx(N*N,{-1,-1});
vector<pii>mn(N*N,{2000,2000});
vector<int>sz(N*N,0);
queue<pii>q;
int cnt=0;
void bfs(int x,int y){
//cerr<<x<<" "<<y<<endl;
q.push({x,y});
st[x][y]=true;
sz[cnt]++;
mx[cnt].first=max(mx[cnt].first,x);
mx[cnt].second=max(mx[cnt].second,y);
// mx[cnt]=max(mx[cnt],{x,y});
// mn[cnt]=min(mn[cnt],{x,y});
mn[cnt].first=min(mn[cnt].first,x);
mn[cnt].second=min(mn[cnt].second,y);
while(q.size()){
auto t=q.front();q.pop();
for(int i=0;i<4;i++){
int tx=t.first+dx[i];
int ty=t.second+dy[i];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]&&!st[tx][ty]){
q.push({tx,ty});st[tx][ty]=true;
sz[cnt]++;
mx[cnt].first=max(mx[cnt].first,tx);
mx[cnt].second=max(mx[cnt].second,ty);
mn[cnt].first=min(mn[cnt].first,tx);
mn[cnt].second=min(mn[cnt].second,ty);
//mx[cnt]=max(mx[cnt],{tx,ty});
//mn[cnt]=min(mn[cnt],{tx,ty});
}
}
}
// cerr<<sz[cnt]<<endl;
// cerr<<mx[cnt].first<<" "<<mx[cnt].second<<endl;
// cerr<<mn[cnt].first<<" "<<mn[cnt].second<<endl;
// cerr<<"***************************"<<endl;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char c;cin>>c;
if(c=='.')a[i][j]=1;
else a[i][j]=0;
//cerr<<a[i][j]<<" ";
}
//cerr<<endl;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]==0||st[i][j])continue;
bfs(i,j);
cnt++;
}
}
int res=0;
//cerr<<cnt<<endl;
for(int i=0;i<cnt;i++){
int l=mx[i].first-mn[i].first+1;
int r=mx[i].second-mn[i].second+1;
if(l*r==sz[i]){
res++;
//cerr<<l<<" "<<r<<' '<<sz[cnt]<<endl;
}
}
cout<<res<<endl;
}
signed main() {
cin.tie(0);
ios::sync_with_stdio(false);
int t;
//cin>>t;
t=1;
while (t--) {
solve();
}
return 0;
}
E题:带容量下限的最大连续子数组和
简单无脑做法:
- 枚举每个起点,他的终点是j到n的任意一点。j是从左到右第一个满足当前区间容量大于W的坐标,对于这一过程我们显然需要维护容量的前缀和然后二分。我们希望价值最大,在终点到n中,我们需要找到最大的前缀和,因为起点被我们枚举从而固定,对此我们提前预处理后缀最大前缀和。
// Problem: 可口蛋糕
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/73450/E
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
# define int long long
#define ull unsigned long long
#define pii pair<int,int>
#define baoliu(x, y) cout << fixed << setprecision(y) << x
#define endl "\n"
#define debug1(x) cerr<<x<<" "
#define debug2(x) cerr<<x<<endl
const int N = 1e6+5;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-8;
int n, m;
int a[N];
int w[N],v[N];
int sw[N],sv[N];
int mx[N];
//v是体积,w是价值
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=n;i++)sv[i]=sv[i-1]+v[i];
//for(int i=1;i<=n;i++)cerr<<sv[i]<<" ";
//cerr<<endl;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<=n;i++)sw[i]=sw[i-1]+w[i];
//for(int i=1;i<=n;i++)cerr<<sw[i]<<" ";
//cerr<<endl;
for(int i=n;i>=1;i--){
if(i==n)mx[i]=sw[i];
else mx[i]=max(mx[i+1],sw[i]);
}
//for(int i=1;i<=n;i++)cerr<<mx[i]<<" ";
//cerr<<endl;
int ans=-1e18;
for(int i=1;i<=n;i++){
int l=i;
int val=sv[i-1]+m;
int r=lower_bound(sv+1,sv+n+1,val)-sv;
if(sv[r]-sv[l-1]<m)continue;
// cerr<<l<<" "<<r<<endl;
int res=mx[r]-sw[l-1];
//cerr<<l<<" "<<r<<" "<<res<<endl;
ans=max(ans,res);
}
cout<<ans<<endl;
}
signed main() {
cin.tie(0);
ios::sync_with_stdio(false);
int t;
//cin>>t;
t=1;
while (t--) {
solve();
}
return 0;
}
标程做法:首先枚举左端点,然后根据 $W$ 的限制求解出右端点的最小值(双指针递推),记为 $r$。
那么此时问题转变为:以 $r$ 为左端点的最大子段和(通过倒序 dp 预处理即可)。
时间复杂度:$O(n)$
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 爱飞鱼的blog!