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没有任何关系,他们满足的条件只有在同一个颜色的集合中,并且相同奇偶。

所以对于同颜色且同奇偶的一个集合来说,每一个元素产生的奉献是

\[x_i*[(size-2)*(v_i)+\sum_j^n{v_j}] \]

提前预处理前缀和还有元素个数

#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;
}

先把例题做完了以后再看……

------------恢复内容结束------------

posted @ 2022-04-09 14:58  SSZX_loser_lcy  阅读(29)  评论(0编辑  收藏  举报