1🐋🐋造房子(星耀;单调栈)
时间限制:1秒
占用内存:128M
🐟题目思路
我们观察,从左往右看每列的0,如果有相连的,那么就可以将左边列的方案数继承过来,然后加上当前位置的方案数。我们用单调栈来模拟每一列的继承过程,判断方案数,这样,如果单调递增,那么直接集成前边的,如果有低下来的列的情况,那么就pop出不合适的点,根据满足递增情况的点得到方案数。
🐟代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3600;
int n,a[N][N],f[N],ans;//a,存放数组;f,存放每列最下方的那个1的行数
signed main( )
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++) cin>>a[i][j];
}
for(int i=1;i<=n;i++)//遍历每行
{
stack<pair<int,int> > st;//单调递增栈
for(int j=1;j<=n;j++)//遍历每列
{
if(a[i][j]) f[j]=i;//记录到该行为止,每列的1的最低位置,也就是以当前行为基点,往上能到达的最大高度
while(!st.empty()&&f[st.top().first]<f[j])//只要是遇到更低的1的列,前边列的都会被pop出来
{
st.pop();
//cout<<"pop了"<<endl;
}
if(st.empty())//栈空
{
st.push({j,0});//将0的点入栈
st.top().second=(i-f[st.top().first])*st.top().first;//高度*长度
//cout<<i-f[st.top().first]<<st.top().first<<endl;
}
else
{
pair<int,int> temp=st.top();
st.push({j,0});
st.top().second=temp.second+(i-f[st.top().first])*(st.top().first-temp.first);
//cout<<temp.second<<i-f[st.top().first]<<st.top().first-temp.first<<endl;
}
ans+=st.top().second;
//cout<<st.top().second<<endl;
}
}
cout<<ans;
return 0;
}
2🐋🐋交换排列(钻石;并查集)
时间限制:1秒
占用内存:64M
🐟题目思路
可以交换位置的这些数的相对位置可以随意变换,这些数就构成一个集合,也就是说集合中的数在这些位置上可以随意排列,那么最大字典序的排列,就是将集合中的数按从大到小排列放入相应位置。所以考虑使用并查集,同时需要大根堆来得到每次的最大数。
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,num[N],f[N];
priority_queue<int> q[N];//因为我要得到每个集合里的最大值,所以用大根堆
int find(int x)
{
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void merge(int i,int j)
{
int x=find(i),y=find(j);
if(x!=y) f[x]=y;
}
int main( )
{
cin>>n>>m;
for(int i=1;i<=n;i++)//输入并初始化数字数组
{
cin>>num[i];
f[i]=i;
}
for(int i=1;i<=m;i++)//输入可交换关系,并生成并查集
{
int x,y;
cin>>x>>y;
merge(x,y);
}
for(int i=1;i<=n;i++) q[find(i)].push(num[i]);//这个根所在的集合里有这些数
for(int i=1;i<=n;i++)
{
//得到每个集合中的当前剩余最大数并输出
int a=find(i);
cout<<q[a].top()<<" ";
q[a].pop();
}
return 0;
}
3🐋🐋搭积木(钻石;并查集)
时间限制:1秒
占用内存:128M
🐟题目思路
这个使用并查集,将相连的1并入一个集合,如果最后全连接(也就是只有一个集合)并且最后一行的1的个数大于等于2,那么就是Yes,否则就是No。
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int f[N*N],n,m,cnt=0,root;
char mp[N][N];
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
int find(int x)
{
if(x==f[x]) return x;
else return f[x]=find(f[x]);
}
void merge(int i,int j)
{
int x=find(i),y=find(j);
if(x!=y) f[x]=y;
}
int convert(int x,int y)//将二维数组转换为一维数组
{
return (x-1)*m+y;
}
int main( )
{
cin>>n>>m;
for(int i=1;i<=n*m;i++) f[i]=i;
for(int i=1;i<=n;i++) cin>>(mp[i]+1);//+1是为了从1开始输入
//先判断最后一行的1的个数
for(int i=1;i<=m;i++)
{
if(mp[n][i]=='1') cnt++;
}
if(cnt<2)
{
cout<<"No";
return 0;
}
//处理其他位置
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(mp[i][j]=='0') continue;
if(!root) root=convert(i,j);//随便找了个点用于在后边判断是否全连通
//遍历上下左右格子,判断集合情况
for(int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(x<1||y<1||x>n||y>m) continue;
if(mp[x][y]=='1') merge(convert(i,j),convert(x,y));//相连,并入集合
}
}
}
root=find(root);//找到根节点判断是否全连接
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(mp[i][j]=='1')
{
if(find(convert(i,j))!=root)//并没有全连通
{
cout<<"No";
return 0;
}
}
}
}
cout<<"Yes";
return 0;
}
4🐋🐋愤怒的象棚(星耀;单调队列+前缀和|差分)
时间限制:1秒
占用内存:128M
🐟题目思路
这道题目考虑使用前缀和得到各区间最大值之和。使用单调队列,要求队列中数据由大到小排列,找到各区间最大值,详情请看注释和视频讲解。
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e6+5;
int n,m,A,a[N],b[N],c[N],sumb[N],sumc[N],ans=-0x3f3f3f3f;
//b[i]表示以i结尾的子区间不包含新象,c[i]表示以i结尾的子区间包含新象,sumb和sumc是前缀和
void getmax(int n,int m)//处理b数组的单调队列,需要这个队列中的数越来越小
{
deque<int> q;
for(int i=1;i<=n;i++)
{
if(!q.empty()&&q.front()<=i-m) q.pop_front();//不该在当前区间的去掉他
while(!q.empty()&&a[i]>=a[q.back()]) q.pop_back();//不满足单调队列的去掉他
q.push_back(i);
if(i>=m) b[i]=a[q.front()],sumb[i]=sumb[i-1]+b[i];//b记录当前区间的最大值,sumb记录各区间最大值的前缀和
}
}
void getmax2(int n,int m)//处理c数组的单调队列
{
deque<int> q;
for(int i=1;i<=n;i++)
{
if(!q.empty()&&q.front()<=i-m) q.pop_front();
while(!q.empty()&&a[i]>=a[q.back()]) q.pop_back();
q.push_back(i);
if(i>=m) c[i]=max(A,a[q.front()]),sumc[i]=sumc[i-1]+c[i];//和b唯一的区别是加入了新象,所以c需要与当前区间最大值进行比较
}
}
int main( )
{
cin>>n>>m>>A;
for(int i=1;i<=n;i++) cin>>a[i];
getmax(n,m);
getmax2(n,m-1);//因为有一头新象,所以每个区间只需要m-1头象,再加上这头新象就行了
for(int i=1;i<=n+1;i++)
{
if(i<=m) ans=max(ans,sumc[i+m-2]+sumb[n]-sumb[i+m-2]);
else if(i>=n-m+2) ans=max(ans,sumb[i-1]+sumc[n]-sumc[i-2]);
else ans=max(ans,sumb[i-1]+sumc[i+m-2]-sumc[i-2]+sumb[n]-sumb[i+m-2]);
}
cout<<ans;
return 0;
}
5🐋🐋区间最大值(钻石;哈希表+STL)
时间限制:1秒
占用内存:256M
🐟题目思路
遍历所有子序列,使用map记录每个数在序列中出现的次数,将区间内出现次数为1的数放入set,由于set的去重性和默认从小到大排序,可以用rbegin函数得到最后一个元素也就是值最大元素的指针,就可以得到结果了。
需要注意的地方是,每次区间滑动,都要对当前区间前一个元素和当前元素进行判断。
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,k,a[N];
map<int,int> mp;//记录出现次数
set<int> s;//去重
int main( )
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=k;i++) mp[a[i]]++;//记录当前小区间里每个数的出现次数
for(int i=1;i<=k;i++)
{
if(mp[a[i]]==1) s.insert(a[i]);//将出现次数为1的数摘出来
}
if(s.empty()) cout<<"-1 ";//如果没有出现次数为1的数(符合要求的数),直接输出-1
else cout<<*s.rbegin()<<" ";//获取set中最后一个元素(最大值元素)的值
for(int i=k+1;i<=n;i++)
{
//我要判断第一个数的个数,因为如果是2,那么在set中将不存在这个数;如果是1,那么要将她从set中去掉;如果是更大的数,那么减去1之后仍然不会是1,就不用放入set
if(mp[a[i-k]]==1) s.erase(a[i-k]);
if(mp[a[i-k]]==2) s.insert(a[i-k]);
mp[a[i-k]]--;
//对当前这个数字进行处理,因为是用到了上一组的k-1个数,所以这里只需要用当前i这个数入区间即可
if(mp[a[i]]==0) s.insert(a[i]);
if(mp[a[i]]==1) s.erase(a[i]);
mp[a[i]]++;
if(s.empty()) cout<<"-1 ";
else cout<<*s.rbegin()<<" ";
}
return 0;
}
6🐋🐋交换序列(钻石;并查集)
时间限制:1秒
占用内存:256M
🐟题目思路
和第2题相似,如果i和对应的目标b[i]在一个集合里,那么就可以实现向目标数组的转化,否则不行。
🐟代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,f[N],b[N],d[N];
int find(int x)
{
if(x==f[x]) return x;
return f[x]=find(f[x]);
}
void merge(int i,int j)
{
int x=find(i),y=find(j);
if(x!=y) f[x]=y;
}
int main( )
{
cin>>n;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=n;i++) cin>>b[i];//目标数组
for(int i=1;i<=n;i++) cin>>d[i];//位置转换数组
for(int i=1;i<=n;i++)//构建并查集
{
if(i-d[i]>=1) merge(i,i-d[i]);
if(i+d[i]<=n) merge(i,i+d[i]);
}
for(int i=1;i<=n;i++)
{
if(find(i)!=find(b[i]))//当前元素i和目标b[i]不在一个集合里,那么就无法实现
{
cout<<-1;
return 0;
}
}
cout<<1;
return 0;
}
⭐创作不易,点个赞吧~
⭐点赞收藏不迷路~