ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

刷题题解(常见优化技巧)

2022-04-16 10:02:59  阅读:138  来源: 互联网

标签:10 技巧 int 题解 mwrite MAXN inline getchar 刷题


刷题题解(常见优化技巧)

Luogu P1102 A-B数对

传送门

题意

给出一个数列 \(a_n\) 和一个数字 \(c\),需要你计算出所有满足 \(a_i-a_j=c\) 的数对 \((i,j)\) 的个数

其中 \(1\le n\le 2\times10^5,\ a_i\le 2^{30},\ 1\le c\le 2\times10^5\)

题解

先排个序,然后去重再统计一下相同数字出现的次数,然后双指针扫描一遍统计答案就行了

每次找到满足条件的两个数之后把答案加上两个数出现次数的乘积就行

复杂度 \(\Theta(n\log n)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(long long a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(long long a,char c)
{
	mwrite(a);
	putchar(c);
}
#define MAXN 200005
int n,c,cnt;
int aa[MAXN];
long long ans;
struct Num
{
	int cnt,v;
	Num():cnt(0),v(-1){}
	bool operator <(const Num& x)const{return v<x.v;}
	bool operator -(const Num& x)const{return v-x.v;}
}a[MAXN];
signed main()
{
	n=in,c=in;
	for(int i=1;i<=n;++i) aa[i]=in;
	sort(aa+1,aa+n+1);
	for(int i=1,t1;i<=n;++i) ((t1=aa[i])==a[cnt].v)?(++a[cnt].cnt):(++cnt,a[cnt].cnt=1,a[cnt].v=t1);
	int L=1,R=2;
	while(R<=cnt)
	{
		if(L==R)++R;
		if(a[R].v-a[L].v==c) ans+=(1ll*a[L].cnt*a[R].cnt),++L,++R;
		else if(a[R].v-a[L].v>c) ++L;
		else ++R;
	}
	cout<<ans;
    return 0;
}

Luogu P1638 逛画展

传送门

题意

给出两个数字 \(n,m\) 一个数列 \(a_n\),需要找到两个数 \(i,j\) 使得在 \(j-i\) 最小的前提下 \(a_i\) 到 \(a_j\) 包含了从 \(1\) 到 \(m\) 的所有数字了,保证有解,若有多组解则输出 \(i\) 最小的那一组

题解

尺取法

先开个桶暴力找到第一个 \(j\) 使得 \(a_1\) 到 \(a_j\) 包含了从 \(1\) 到 \(m\) 的所有数字,然后不断尝试向右移动左端点,一旦不满足条件就将左端点向左移动一次并向右移动一次右端点,然后再不断尝试向右移动左端点即可,中途记得更新答案

复杂度 \(\Theta(n)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
using namespace std;
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<48)c=getchar();
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x;
}
#define MAXN 1000006
#define MAXM 2003
#define INF 1000000000
int bina[MAXM],a[MAXN];
signed main()
{
	register int n=in,m=in,okcnt=0,minn=INF,ansa,ansb,L=1,R=1;
	for(register int i=1;i<=n;++i)
		if(okcnt<m)
		{
			if((++bina[a[i]=in])==1) ++okcnt;
			if(okcnt==m) R=i;
		}
		else a[i]=in;
	while(R<=n)
	{
		while(--bina[a[L]]) ++L;
		++bina[a[L]];
		if(R-L+1<minn) minn=R-L+1,ansa=L,ansb=R;
		++bina[a[++R]];
	}
	cout<<ansa<<' '<<ansb;
    return 0;
}

Luogu P1115 最大子段和

传送门

题意

给出一个长度为 \(n\) 的序列 \(a_n\),选出其中连续且非空的一段使得这段和最大

其中 \(1\le n\le 2\times10^5,\ -10^4\le a_i\le 10^4\)

题解

先求一遍前缀和,记为 \(s_n\),对于以 \(a_i\) 结尾的最大子段和的答案就等于 \(s_i-\min\limits_{k=1}^{i-1}s_k\),由于有可能是从 \(a_1\) 到 \(a_i\),所以答案是 \(s_i-\min(0,\min\limits_{k=1}^{i-1}s_k)\)

从 \(i=1\) 开始枚举,每次取最小值时都只会新加入一个 \(s_i\),所以只用把 \(s_i\) 与上一次的最小值比较就行了,细节看代码

复杂度 \(\Theta(n)\)

C++ Code

#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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
#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]);
	write(Ans,'\n');
    return 0;
}

Luogu P7072 [CSP-J2020] 直播获奖

传送门

题意

给出两个数字 \(p\) 和 \(w\) 后依次给出 \(n\) 个数字,对于每一个给出的数字都需要求出这个数以及之前的数字中排名为 \(p\times w\%\) 的数字是多少

题解

开两个堆,一个大根堆一个小根堆,大根堆维护更小的 \(1-p\times w\%\) 个数字,小根堆维护最大的 \(p\times w\%\) 个数字,每次插入进堆,如果不满足性质就把小根堆堆顶弹出加入大根堆或者把大根堆堆顶弹出加入小根堆,显然每次插入复杂度都是 \(\Theta(\log n)\) 的

复杂度 \(\Theta(n\log n)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(long long a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(long long a,char c)
{
	mwrite(a);
	putchar(c);
}
#define MAXN 200005
int n,c,cnt;
int aa[MAXN];
long long ans;
struct Num
{
	int cnt,v;
	Num():cnt(0),v(-1){}
	bool operator <(const Num& x)const{return v<x.v;}
	bool operator -(const Num& x)const{return v-x.v;}
}a[MAXN];
signed main()
{
	n=in,c=in;
	for(int i=1;i<=n;++i) aa[i]=in;
	sort(aa+1,aa+n+1);
	for(int i=1,t1;i<=n;++i) ((t1=aa[i])==a[cnt].v)?(++a[cnt].cnt):(++cnt,a[cnt].cnt=1,a[cnt].v=t1);
	int L=1,R=2;
	while(R<=cnt)
	{
		if(L==R)++R;
		if(a[R].v-a[L].v==c) ans+=(1ll*a[L].cnt*a[R].cnt),++L,++R;
		else if(a[R].v-a[L].v>c) ++L;
		else ++R;
	}
	cout<<ans;
    return 0;
}

Luogu P2671 [NOIP2015 普及组] 求和

传送门

题意

一条狭长的纸带被均匀划分出了 \(n\) 个格子,格子编号从 \(1\) 到 \(n\)。每个格子上都染了一种颜色 \(color_i\) 用 \([1,m]\) 当中的一个整数表示),并且写了一个数字 \(number_i\)。

定义一种特殊的三元组:\((x,y,z)\),其中 \((x,y,z)\) 都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:

  1. \(x,y,z\) 是整数,\(x<y<z,\ y-x=z-y,\ y−x=z−y\)
  2. \(color_x=color_z\)

满足上述条件的三元组的分数规定为 \((x+z) \times (number_x+number_z)\)。整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 \(10007\) 所得的余数即可。

题解

不难发现 \(x+z\) 必须是偶数才有可能满足情况,所以 \(x,z\) 的奇偶性相同,考虑开 \(2m\) 个 \(\text{vector}\),对于每种颜色把出现位置下标按照奇数和偶数分类,分别放进 \(\text{vector}\) 里,对于每个 \(\text{vector}\) 里的所有元素,推一推式子可以发现 \(\text{ans}=(\sum{\text{number}_i})\times(\sum{\text{pos}_i})+(\text{vector.size()}-2)\times \sum(\text{pos}_i\times \text{number}_i)\)

复杂度 \(\Theta(n)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
const int MAXN=1e5+5,Mod=1e4+7;
vector<int>col[MAXN][2];
int num[MAXN];
int n,m;
typedef long long ll;
ll ans;
inline void solve(int cl,int k)
{
	ll ntot=0ll,ptot=0ll,sz=col[cl][k].size();
	for(auto i:col[cl][k])
		ntot=(ntot+num[i])%Mod,
		ptot=(ptot+i)%Mod,
		ans=(ans+(sz-2ll)*i*num[i])%Mod;
	ans=(ans+ntot*ptot)%Mod;
}
signed main()
{
	n=in,m=in;
	for(int i=1;i<=n;++i) num[i]=in;
	for(int i=1;i<=n;++i) col[in][i&1].push_back(i);
	for(int i=1;i<=m;++i) solve(i,0),solve(i,1);
	cout<<ans;
    return 0;
}

Luogu P4147 玉蟾宫

传送门

题意

给一个 \(n\times m\) 的图和一些障碍点,求最大子矩形面积

其中 \(1\le n,m\le 10^3\)

题解

先扫描一遍找出每个点最左边最右边和最上边能到达哪个点,分别记为 \(l_{i,j},\ r_{i,j},\ h_{i,j}\),然后从上往下更新,每一个点令 \(l_{i,j}=\max(l_{i,j},l_{i-1,j}),\ r_{i,j}=\min(r_{i,j},r_{i-1,j})\),然后把答案和 \((r_{i,j}-l_{i,j}+1)h_{i,j}\) 取个 \(\max\) 即可,可以画图理解一下,正确性比较显然

复杂度 \(\Theta(mn)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
#define MAXN 1003
bitset<MAXN>mm[MAXN];
int n,m,ans;
int l[MAXN][MAXN],r[MAXN][MAXN],h[MAXN][MAXN];
signed main()
{
	n=in,m=in;
	char tmp='@';
	for(int i=1;i<=n;++i)
	{
		mm[i].reset();
		for(int j=1;j<=m;++j)
		{
			l[i][j]=r[i][j]=j;
			h[i][j]=1;
			while(tmp!='R'&&tmp!='F') tmp=getchar();
			mm[i][j]=(tmp=='F');
			if(mm[i][j]&&mm[i][j-1]) l[i][j]=l[i][j-1];
			if(mm[i][j]&&mm[i-1][j]) h[i][j]+=h[i-1][j];
			tmp=getchar();
		}
	}
	for(int i=1;i<=n;++i)
		for(int j=m;j;--j)
			if(mm[i][j]&&mm[i][j+1])
				r[i][j]=r[i][j+1];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			if(!mm[i][j])continue;
			if(mm[i-1][j]) l[i][j]=max(l[i][j],l[i-1][j]),r[i][j]=min(r[i][j],r[i-1][j]);
			ans=max(ans,(r[i][j]-l[i][j]+1)*h[i][j]);
		}
	write(3*ans,'\n');
    return 0;
}

Luogu P2866 [USACO06NOV]Bad Hair Day S

传送门

题意

给定 \(n\) 头牛和它们的身高 \(h_i\),对于每头牛来说,它能看见它前面连续的比它矮的牛,也就是说它前面的第一头比它高的牛和这头牛前面的所有牛它都看不见,请你求出每头牛能看见牛的数量和总和

题解

换个思路,我们求每头牛能被多少牛看见,维护一个单调递减的单调栈,每一次弹出就说明这头牛能被栈里剩余的所有牛看见,而后面插入的牛它一定都无法看见,所以把答案加上栈中元素的数量即可

小细节,最后记得把栈清空,因为里面的牛对答案还有贡献

复杂度 \(\Theta(n)\)

C++ Code

#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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
int n,ans;
stack<int>sta;
signed main()
{
	n=in;
	for(int i=1,tmp;i<=n;++i)
	{
		tmp=in;
		while((!sta.empty())&&tmp>=sta.top()) sta.pop(),ans+=sta.size();
		sta.push(tmp);
	}
	while(!sta.empty())sta.pop(),ans+=sta.size();
	write(ans,'\n');
    return 0;
}

Luogu P1950 长方形

传送门

题意

给定一个 \(n\times m\) 的矩阵和一些障碍点,请求出一共有多少个不同的没有包含障碍点的矩形

其中 \(1\le n,m\le 10^3\)

题解

预处理出每个点向上最多能取多少格,记为 \(h_{i,j}\)

枚举底边,对于每一行,单调栈找到每个点左边第一个 \(h\) 不大于它的数和右边第一个 \(h\) 小于它的数

答案累加上 \((i-l)\times(r-i)\times h_i\) 即可

为什么不会算重呢?因为如果重复,那么左端点的 \(h\) 就应该和之前某次左端点的 \(h\) 相同,但是我们找左边第一个 \(h\) 不大于它的数,就可以保证左端点在遇到连续相等的 \(h\) 的时候只会取最靠近当前枚举的点的那个点,可以画个图感性理解一些

复杂度 \(\Theta(mn)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
#define MAXN 1001
typedef long long ll;
ll ans;
int n,m;
bitset<MAXN>mm[MAXN];
int h[MAXN][MAXN],l[MAXN],r[MAXN];
signed main()
{
	n=in,m=in;
	char ch='@';
	for(int i=1;i<=n;++i)
	{
		mm[i].reset();
		for(int j=1;j<=m;++j,ch=getchar())
		{
			while(ch!='.'&&ch!='*')ch=getchar();
			mm[i][j]=(ch=='.');
			h[i][j]=(mm[i][j]?((mm[i-1][j]?h[i-1][j]+1:1)):0);
		}
	}
	stack<int>tmp;
	while(!tmp.empty()) tmp.pop();
	for(int i=1;i<=n;++i)
	{
		for(int j=m;j;--j)
		{
			while((!tmp.empty())&&h[i][tmp.top()]>=h[i][j]) l[tmp.top()]=j,tmp.pop();
			tmp.push(j);
		}
		while(!tmp.empty()) l[tmp.top()]=0,tmp.pop();
		for(int j=1;j<=m;++j)
		{
			while((!tmp.empty())&&h[i][tmp.top()]>h[i][j]) r[tmp.top()]=j,tmp.pop();
			tmp.push(j);
		}
		while(!tmp.empty()) r[tmp.top()]=m+1,tmp.pop();
		for(int j=1;j<=m;++j)
			ans+=1ll*(j-l[j])*(r[j]-j)*h[i][j];
	}
	cout<<ans;
    return 0;
}

Luogu P2032 扫描

传送门

题意

给出一个数列 \(a_n\) 和一个数字 \(k\),求出每连续 \(k\) 个数中的最大值

其中 \(1\le n,k\le 2\times10^6\)

题解

注意到只有 \(2\times 10^6\),所以直接开个 \(\text{set}\) 模拟就行(什么单调队列啊?)

正经做法,维护一个单调递减的单调队列,用 \(\text{pair}\) 存一下原数列上的位置和值,过时了就出队列,每次插入一个数,队首就是答案

复杂度 \(\Theta(n)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
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;
}
inline void mwrite(int a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(int a,char c)
{
	mwrite(a<0?(putchar('-'),a=-a):a);
	putchar(c);
}
#define mp make_pair 
#define pb emplace_back 
int n,len;
deque<pair<int,int> >q;
signed main()
{
	n=in,len=in;
	for(int i=1,tmp;i<len;++i)
	{
		tmp=in;
		while((!q.empty())&&q.back().first<tmp) q.pop_back();
		q.pb(mp(tmp,i));
	}
	for(int i=len,tmp;i<=n;++i)
	{
		tmp=in;
		while((!q.empty())&&q.back().first<tmp) q.pop_back();
		q.pb(mp(tmp,i));
		while((!q.empty())&&i-q.front().second+1>len) q.pop_front();
		write(q.front().first,'\n');
	}
    return 0;
}

Luogu P2216 [HAOI2007]理想的正方形

传送门

题意

给出一个 \(a\times b\) 矩阵,找到一个 \(n\times n\) 的正方形区域,使得该区域所有数中的最大值和最小值的差最小

其中 \(1\le a,b\le 10^3,\ 1\le n\le 100\)

题解

发现是一个二维 RMQ,要写二维ST表,可是我太懒了,怎么办呢?那就不倍增了,直接dp,每一次只新增 \(1\) 长度,卡一卡就艹过去了

复杂度 \(\Theta(abn)\)

C++ Code

#include<bits/stdc++.h>
#define in read()
#define rg register
using namespace std;
#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<16,stdin),S==T)?EOF:*S++)
char B[1<<16],*S=B,*T=B;
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<48)c=getchar();
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x;
}
#define INF 2100000000
const int MAXN=1e3+2;
int mm[MAXN][MAXN];
int vmax[MAXN][MAXN],vmin[MAXN][MAXN];
inline int max(rg int a,rg int b){return a<b?b:a;}
inline int min(rg int a,rg int b){return a>b?b:a;}
signed main()
{
	rg int a=in,b=in,n=in,ans=INF;
	for(rg int i=1;i<=a;++i)
		for(rg int j=1;j<=b;++j)
			mm[i][j]=vmax[i][j]=vmin[i][j]=in;
	for(rg int k=1;k^n;++k)
		for(rg int i=1;i<=a;++i)
			for(rg int j=1;j<=b;++j)
				vmax[i][j]=max(vmax[i+1][j+1],max(mm[i][j],max(vmax[i][j+1],vmax[i+1][j]))),
				vmin[i][j]=min(vmin[i+1][j+1],min(mm[i][j],min(vmin[i][j+1],vmin[i+1][j])));
	for(rg int i=1,tmp1=a-n+2;i<tmp1;++i)
		for(rg int j=1,tmp2=b-n+2;j<tmp2;++j)
			ans=min(ans,vmax[i][j]-vmin[i][j]);
	cout<<ans;
    return 0;
}

该文为本人原创,转载请注明出处

博客园传送门

标签:10,技巧,int,题解,mwrite,MAXN,inline,getchar,刷题
来源: https://www.cnblogs.com/cmy-blog/p/shua-ti-1.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有