NOIP-1 优化技巧
------------恢复内容开始------------
前言
蒟蒻在教练的逼迫下,被迫放弃摆烂行动,开始做题。
然而黄橙都可以卡掉我……
1.A-B 数对
解法:map 哈希映射暴力冲!
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,c;
int a[200001];
map<int,int> q;//哈希万岁!
signed main(){
cin>>n>>c;
for(int i=1;i<=n;i++){
cin>>a[i];
q[a[i]]++;
a[i]-=c;//加减皆可
}
int ans=0;
for(int i=1;i<=n;i++){
ans+=q[a[i]];
}
cout<<ans<<endl;
return 0;
}
2.P1638 逛画展
解法:先遍历找到满足出现所有画家的最小的右端点,再找到左端点。记录一下后向后遍历找到有没有更小的区间,每次右端点右移后更新左端点。
代码:
#include<bits/stdc++.h>
using namespace std;
int m[2001],num;
int n[1000000];
int main()
{
int R=0,L=1,N,M,t,i=0,ansL,ansR;
cin>>N>>M;
for(i=1;i<=N;i++)
scanf("%d",n+i);
i=1;
while(num<=M)//找到第一个右端点
{
if(m[n[i]]==0)num++;
m[n[i]]++;
R++;i++;
if(num==M) break;
}
while(m[n[L]]>1)//推掉不合适的左端点
m[n[L++]]--;
ansL=L;ansR=R;
while(i<=N)//向后面扫
{
m[n[i]]++;
R++;
i++;
while(m[n[L]]>1)
m[n[L++]]--;
if(ansR-ansL>R-L)
{
ansR=R;
ansL=L;
}
}
printf("%d %d",ansL,ansR);
}
3.P1115 最大子段和
解法:
考虑以一个端点结束的区间的最大子段和,即为这个端点的前缀和减去前面最小的前缀和,每一更新即可。
#include<bits/stdc++.h>
#define in read()
#define int long long
using namespace std;
inline int read()
{
char c=getchar();
int x=0,f=1;
while(c<48){if(c=='-')f=-1;c=getchar();}
while(c>47)x=(x*10)+(c^48),c=getchar();
return x*f;
}
#define MAXN 200005
#define INF 1000000000
int n,minn,Ans;
int sum[MAXN];
signed main()
{
n=in;
minn=min(0ll,(Ans=sum[1]=in));
for(int i=2;i<=n;++i) sum[i]=sum[i-1]+in,Ans=max(Ans,sum[i]-minn),minn=min(minn,sum[i]);//计算出以该节点结尾的最大字段和
printf("%d\n",Ans);
return 0;
}
4.P7072 [CSP-J2020] 直播获奖
解法:
采用一个新奇的算法:对顶堆。
对顶堆由一个大顶堆与一个小顶堆组成,动态维护单调区间第k大数或第k小数。
此题由于是降序排列,小顶堆在上大顶堆在下,中间为分点。
基本操作:
1.插入元素
void push(int num)
{
if (num >= ma_hp.top())
mi_hp.push(num);
else ma_hp.push(num);
qwq();//调整小顶堆元素个数
}
2.交换元素
mi_hp.push(ma_hp.top());
ma_hp.pop();
ma_hp.push(mi_hp.top());
mi_hp.pop();
3.查询
mi_hp.top();
代码:
#include<bits/stdc++.h>
using namespace std;
priority_queue<int> ma_hp;
priority_queue<int, vector<int>, greater<int> >mi_hp;
int n,w,now,num;
void qwq(){
if(mi_hp.size()<now){
mi_hp.push(ma_hp.top());
ma_hp.pop();
}
if(mi_hp.size()>now){
ma_hp.push(mi_hp.top());
mi_hp.pop();
}
}
void push(int num){
if(num>=ma_hp.top()) mi_hp.push(num);
else ma_hp.push(num);
qwq();
}
int main(){
scanf("%d%d",&n,&w);
ma_hp.push(0);
for(int p=1;p<=n;p++){
now=max(1,p*w/100);
scanf("%d",&num);
push(num);
printf("%d ",mi_hp.top());
}
return 0;
}
2.P2671 [NOIP2015 普及组] 求和
解法:
有一点难度,我们发现数对的数量和y没有任何关系,他们满足的条件只有在同一个颜色的集合中,并且相同奇偶。
所以对于同颜色且同奇偶的一个集合来说,每一个元素产生的奉献是
提前预处理前缀和还有元素个数
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
const int mod=10007;
int a[N],b[N];
int n,m;
int ans=0;
int c[N],x[N],sum[N][2],s[N][2];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
s[b[i]][i%2]++;
sum[b[i]][i%2]+=a[i]%mod;
sum[b[i]][i%2]%=mod;
}
for(int i=1;i<=n;i++){
ans=(ans+i*((s[b[i]][i%2]-2)*a[i]%mod+sum[b[i]][i%2]))%mod;
}
cout<<ans<<endl;
return 0;
}
6.P4147 玉蟾宫
解法:此题用悬线法来做。找到每个点向左和向右走的最远的点,还有上面最远的点。
然后遍历每一个点,找出它的矩形的面积并更新答案。
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int N=2001;
int n,m;
char a;
int Map[N][N];
int sum[N][N];
int l[N][N],r[N][N],up[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a;
if(a=='R'){
Map[i][j]=0;
}else{
Map[i][j]=1;
}
up[i][j]=1;
r[i][j]=l[i][j]=j;
}
}
for(int i=1;i<=n;i++){
for(int j=2;j<=m;j++){
if(Map[i][j-1]==1&&Map[i][j]==1) l[i][j]=l[i][j-1];
}
}
for(int i=1;i<=n;i++){
for(int j=m-1;j>=1;j--){
if(Map[i][j+1]==1&&Map[i][j]==1) r[i][j]=r[i][j+1];
}
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i>1&&Map[i][j]==1&&Map[i-1][j]==1){
l[i][j]=max(l[i-1][j],l[i][j]);
r[i][j]=min(r[i-1][j],r[i][j]);
up[i][j]=up[i-1][j]+1;
}
if(Map[i][j]==1)ans=max(ans,(r[i][j]-l[i][j]+1)*up[i][j]);
}
}
cout<<3*ans<<endl;
return 0;
}
7.P2866 [USACO06NOV]Bad Hair Day S
解法:单调栈来做,考虑每头牛可一被多少头牛看到。维护一个单调递减的栈,把该点加入后,栈里的元素个数就是这头牛能被多少头牛看到。
#include<bits/stdc++.h>
using namespace std;
int n;
const int N =1e5;
int a[N];
long long ans=0;
stack<int> q;
int main(){
cin>>n;
a[0]=0x3f3f3f3f;
for(int i=1;i<=n;i++){
cin>>a[i];
while(!q.empty()&&a[i]>=q.top()){
q.pop();
}
ans+=1ll*q.size();
q.push(a[i]);
}
cout<<ans<<endl;
return 0;
}
8.P1950 长方形
解法:把问题分到每一行来解决。
在一行中,先统计每个点向上走的最远路程。l_i表示左边高度不大于h_i的最近的列,r_i表示右边高度小于的最近的列。对于l_i到r_i的高为h_i的矩形来说左端点的方案有(i-l_i)种,右端点的方案有(r_i-i)种,高有h_i种。相乘即可。
这里保证不会算重,左右的定义不一样。
#include<bits/stdc++.h>
using namespace std;
char ch;
long long l[1020],r[1020],h[1020],k[1020],n,m,top;
int d[1020][1020];
long long ans;
void ddzl(){
top=0;
for(int i=m;i>=1;i--){//左边第一个不大于h[i]的
while(top!=0&&h[i]<=h[k[top]]) l[k[top]]=i,top--;
top++;
k[top]=i;
}
while(top) l[k[top]]=0,top--;
}
//一个不大于一个小于保证了不会算重,后面的左端点不会到算过的地方去,右端点是终止的地方
void ddzr(){
top=0;
for(int i=1;i<=m;i++){//右边第一个小于的
while(top!=0&&h[i]<h[k[top]]) r[k[top]]=i,top--;
top++;
k[top]=i;
}
while(top) r[k[top]]=m+1,top--;
}
void work(){
ddzl();
ddzr();
for(int i=1;i<=m;i++){
ans+=(i-l[i])*(r[i]-i)*h[i];//左端点的方案与右端点的方案还有高的方案
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>ch;
if(ch=='*') d[i][j]=1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
h[j]++;
if(d[i][j]) h[j]=0;
}
work();
}
cout<<ans;
return 0;
}
9.P2032 扫描
单调队列模板……
#include<bits/stdc++.h>
using namespace std;
int n,k;
const int N=2e6+100;
int a[N];
int q[N],head,tail;
int num[N];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
while(num[head]<i-k+1&&head<=tail) head++;
while(head<=tail&&a[i]>=q[tail]) tail--;
q[++tail]=a[i];
num[tail]=i;
if(i>=k) cout<<q[head]<<endl;
}
return 0;
}
10.P2216 [HAOI2007]理想的正方形
暴力单调队列T了两个点,加上快读就卡过去了……
不得不说自带大常数……
#include<iostream>
#include<cstring>
#define re register
#define in read()
using namespace std;
int a,b,k;
const int N=1e3+100;
int aa[N][N];
int q[N],head=1,tail;
int num[N];
int Fmin_1[N][N],Fmin_2[N][N],Fmax_1[N][N],Fmax_2[N][N];
int ans=0x3f3f3f3f;
inline int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
void init(){
memset(num,0,sizeof num);
memset(q,0,sizeof q);
head=1,tail=0;
}
inline void F_max_1(int x){//每一行的最大值
init();
for(re int i=1;i<=b;i++){
while(num[head]<i-k+1&&head<=tail) head++;
while(head<=tail&&aa[x][i]>=q[tail]) tail--;
q[++tail]=aa[x][i];
num[tail]=i;
if(i>=k) Fmax_1[x][i]=q[head];
}
}
inline void F_max_2(int x){//每一行的最da值
init();
for(re int i=1;i<=a;i++){
while(num[head]<i-k+1&&head<=tail) head++;
while(head<=tail&&aa[i][x]>=q[tail]) tail--;
q[++tail]=aa[i][x];
num[tail]=i;
if(i>=k) Fmax_2[i][x]=q[head];
}
}
void F_min_1(int x){//每一列的
init();
for(re int i=1;i<=b;i++){
while(num[head]<i-k+1&&head<=tail) head++;
while(aa[x][i]<=q[tail]&&head<=tail) tail--;
num[++tail]=i;q[tail]=aa[x][i];
if(i>=k) Fmin_1[x][i]=q[head];
}
}
void F_min_2(int x){
init();
for(re int i=1;i<=a;i++){
while(num[head]<i-k+1&&head<=tail) head++;
while(aa[i][x]<=q[tail]&&head<=tail) tail--;
num[++tail]=i;q[tail]=aa[i][x];
if(i>=k) Fmin_2[i][x]=q[head];
}
}
void check(int x,int y){
int maxx=0,minn=0x3f3f3f3f;
for(re int i=x;x-i+1<=k;i--){
maxx=max(maxx,Fmax_1[i][y]);
minn=min(minn,Fmin_1[i][y]);
}
for(re int i=y;y-i+1<=k;i--){
maxx=max(maxx,Fmax_2[x][i]);
minn=min(minn,Fmin_2[x][i]);
}
ans=min(ans,maxx-minn);
return ;
}
int main(){
a=in,b=in,k=in;;
for(re int i=1;i<=a;i++)
for(re int j=1;j<=b;j++)
aa[i][j]=in;
for(re int i=1;i<=a;i++){//每一行处理
F_min_1(i);
F_max_1(i);
}
for(re int i=1;i<=b;i++){//每一列的处理
F_min_2(i);
F_max_2(i);
}
for(re int i=k;i<=a;i++)//枚举右下端点
for(re int j=k;j<=b;j++)
check(i,j);
cout<<ans<<endl;
return 0;
}
先把例题做完了以后再看……
------------恢复内容结束------------