NOIP2017模拟赛总结(2017.10.26-2017.10.29)

本文精选了算法竞赛中的经典题目,包括背包问题、最短路径、DP、贪心算法等,详细解析了每道题目的解题思路和代码实现,为算法爱好者提供了宝贵的学习资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

嗯,这次是10.26到10.29的题,因为10.27休息了一天,所以还是3天。

2017.10.26 Problem A
题目大意: 有一个容积为VVV的背包和nnn个物品,每个物品有两个参数ai,bia_i,b_iai,bi,表示要装下这个物品需要消耗aia_iai的容积,但装进去后背包的容积会扩大bib_ibi,问存不存在一种装物品的顺序使得能装下所有物品。
做法: 本题需要用到贪心。
首先对于ai≤bia_i\le b_iaibi的物品,按照aia_iai从小到大的顺序取,如果不能取完那就无解,关键是如何处理ai>bia_i>b_iai>bi的物品。
实际上,像这类取东西的题目,可以有一类贪心证法,这里记录下我的证法。
注意到无论按什么顺序取两个物品,取完后的状态都是一样的,那么我们就试图证明,如果取完物品iii后还能取物品jjj(条件1),一定可以推出取完物品jjj后还能取物品iii(条件2),那么先取物品jjj就是更优的。为什么呢?因为在这种情况下,条件1,2满足的情况只有三种:两个都满足,两个都不满足,条件1不满足而条件2满足。显然情况1,2对结果没有影响,关键是情况3,若是这种情况的话,先取iii就一定不能取完所有物品,而先取jjj说不定可以,所以先取jjj一定最优。
我们把条件1和条件2用数学式子表达出来:
条件1:V−ai+bi≥ajV-a_i+b_i\ge a_jVai+biaj
条件2:V−aj+bj≥aiV-a_j+b_j\ge a_iVaj+bjai
在两个条件中VVV的解集分别为V≥ai+aj−biV\ge a_i+a_j-b_iVai+ajbiV≥ai+aj−bjV\ge a_i+a_j-b_jVai+ajbj,要使条件1能推出条件2,那么条件2的解集应包含条件1的解集,即ai+aj−bj≤ai+aj−bia_i+a_j-b_j\le a_i+a_j-b_iai+ajbjai+ajbi,解得bi≤bjb_i\le b_jbibj,由此证得先取bib_ibi较大的物品是最优的,那么就这么取即可。时间复杂度O(nlog⁡n)O(n\log n)O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,T;
ll v;
struct point {ll a,b;} f[100010],g[100010];

bool cmp0(point a,point b)
{
	return a.a<b.a;
}

bool cmp(point a,point b)
{
	return a.b>b.b;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%lld",&n,&v);
		int t1=0,t2=0;
		for(int i=1;i<=n;i++)
		{
			ll a,b;
			scanf("%lld%lld",&a,&b);
			if (b-a>=0) g[++t1].a=a,g[t1].b=b;
			else f[++t2].a=a,f[t2].b=b;
		}
		
		sort(g+1,g+t1+1,cmp0);
		sort(f+1,f+t2+1,cmp);
		bool flag=1;
		for(int i=1;i<=t1;i++)
		{
			if (v<g[i].a) {flag=0;break;}
			v=v-g[i].a+g[i].b;
		}
		if (!flag) {printf("No\n");continue;}
		for(int i=1;i<=t2;i++)
		{
			if (v<f[i].a) {flag=0;break;}
			v=v-f[i].a+f[i].b;
		}
		if (flag) printf("Yes\n");
		else printf("No\n");
	}
	
	return 0;
}

2017.10.26 Problem B
题目大意: 有一个长为nnn的数列,若一个区间[L,R][L,R][L,R]内存在一个数aka_kak,使得对于所有L≤i≤RL\le i\le RLiR,都有ak∣aia_k|a_iakai,那么我们说这个区间是合法的,价值为R−LR-LRL,求价值最大区间的价值以及所有这些区间的左端点。
做法: 本题需要用到枚举。
对于一个点,我们可以暴力求出以它为公因数的最长合法区间的左端点和右端点,这样做的时间复杂度是O(n2)O(n^2)O(n2)的,虽然常数不大,但如果所有数字都相同的话会被卡掉。注意到,如果当前可以扩展到rhtrhtrht这个右端点,那么从这个点到这个右端点之间的这些点实际上求了也没用,那么我们可以直接跳到rht+1rht+1rht+1继续暴力。可以证明最坏情况下时间复杂度为O(nlog⁡n)O(n\log n)O(nlogn),随机情况下复杂度为O(n)O(n)O(n)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,lft[500010]={0},rht[500010]={0},mx=0,num=0;
ll a[500010];
bool vis[500010]={0};

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	
	for(int i=1;i<=n;i=rht[i]+1)
	{
		int j;
		for(j=i;j>=1;j--)
			if (a[j]%a[i]!=0) break;
		lft[i]=j+1;
		for(j=i;j<=n;j++)
			if (a[j]%a[i]!=0) break;
		rht[i]=j-1;
		mx=max(mx,rht[i]-lft[i]);
	}
	
	for(int i=1;i<=n;i++)
		if (rht[i]-lft[i]==mx) vis[lft[i]]=1;
	for(int i=1;i<=n;i++)
		if (vis[i]) num++;
	printf("%d %d\n",num,mx);
	for(int i=1;i<=n;i++)
		if (vis[i]) printf("%d ",i);
	
	return 0;
}

2017.10.26 Problem C
题目大意: 一个n(≤30)×m(≤30)n(\le 30)\times m(\le 30)n(30)×m(30)的棋盘中,要放c(≤10)c(\le 10)c(10)种颜色的棋子,每种颜色的棋子数量可能不同,但总数不超过900900900,不同颜色的棋子不能放在同一行或同一列,求合法的方案数。
做法: 本题需要用到组合数学递推+二维背包。
观察发现,每种颜色的棋子都独占若干行和若干列,如果我们能知道在正好占用若干行若干列时,放若干个棋子的方案数的话,就可以做二维背包了!令f(i,j,k)f(i,j,k)f(i,j,k)kkk个同色棋子正好占用iiijjj列的方案数,那么可得状态转移方程:
f(i,j,k)=Ci×jk−∑p=0i∑q=0jCipCjqf(i−p,j−q,k)f(i,j,k)=C_{i\times j}^k-\sum_{p=0}^i\sum_{q=0}^jC_i^pC_j^qf(i-p,j-q,k)f(i,j,k)=Ci×jkp=0iq=0jCipCjqf(ip,jq,k)
上式是怎么推出来的呢?可以看做,用在i×ji\times ji×j个格子里放kkk个棋子的方案数,减去正好空pppqqq列的方案数。注意到第三维实际上没有必要枚举全部111~900900900,只要求出k=k=k=某种棋子颜色总数的情况就可以了,那么我们删去第三维,上式就是O(cn2m2)O(cn^2m^2)O(cn2m2)的了,可以接受。
接下来就是二维背包了。令g(k,i,j)g(k,i,j)g(k,i,j)为放前kkk种颜色,还剩iiijjj列没有占用的方案数,那么有状态转移方程:
g(k,i,j)=∑p=in∑q=jmCpiCqjf(p−i,q−j,num(k))g(k−1,p,q)g(k,i,j)=\sum_{p=i}^n\sum_{q=j}^mC_p^iC_q^jf(p-i,q-j,num(k))g(k-1,p,q)g(k,i,j)=p=inq=jmCpiCqjf(pi,qj,num(k))g(k1,p,q)
其中num(k)num(k)num(k)为第kkk种颜色的棋子数量,边界条件为g(0,n,m)=1g(0,n,m)=1g(0,n,m)=1,显然答案为∑i=1n∑j=1mg(c,i,j)\sum_{i=1}^n\sum_{j=1}^m g(c,i,j)i=1nj=1mg(c,i,j)。上述的方程应该可以挺容易推出来了,时间复杂度为O(cn2m2)O(cn^2m^2)O(cn2m2),那么预处理出组合数就可以通过该题了。
(原题好像叫做CQOI2011放棋子)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000000009
using namespace std;
int n,m,c,num[11],tot=0;
ll cc[1010][1010],f[35][35]={0},g[2][35][35]={0};

ll power(ll a,ll b)
{
	ll s=1,ss=a;
	while(b)
	{
		if (b&1) s=(s*ss)%mod;
		b>>=1;ss=(ss*ss)%mod;
	}
	return s;
}

ll C(int n,int m)
{
	if (m>n) return 0;
	return cc[n][m];
}

int main()
{
	scanf("%d%d%d",&n,&m,&c);
	for(int i=1;i<=c;i++) scanf("%d",&num[i]),tot+=num[i];
	
	cc[0][0]=1;
	for(int i=1;i<=1000;i++)
	{
		cc[i][0]=1;
		for(int j=1;j<=i;j++)
			cc[i][j]=(cc[i-1][j-1]+cc[i-1][j])%mod;
	}
	
	int now=1,past=0;
	g[past][n][m]=1;
	for(int k=1;k<=c;k++)
	{
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				f[i][j]=C(i*j,num[k]);
				for(int p=0;p<=i;p++)
					for(int q=0;q<=j;q++)
						if (p||q) f[i][j]=(((f[i][j]-((C(i,p)*C(j,q))%mod)*f[i-p][j-q])%mod)+mod)%mod;
			}
		for(int i=0;i<=n;i++)
			for(int j=0;j<=m;j++)
			{
				g[now][i][j]=0;
				for(int p=i;p<=n;p++)
					for(int q=j;q<=m;q++)
						g[now][i][j]=(g[now][i][j]+((((C(p,i)*C(q,j))%mod)*f[p-i][q-j])%mod)*g[past][p][q])%mod;
			}
		swap(now,past);
	}
	
	ll ans=0;
	for(int i=0;i<=n;i++)
		for(int j=0;j<=m;j++)
			ans=(ans+g[past][i][j])%mod;
	printf("%lld\n",ans);
	
	return 0;
}

2017.10.28 Problem A
题目大意: n(≤500000)×nn(\le 500000)\times nn(500000)×n个士兵占成n×nn\times nn×n的方阵,第iii行第jjj列的士兵站在(i,j)(i,j)(i,j),所有士兵身高相同,问从(0,0)(0,0)(0,0)能看到的士兵的数量。
做法: 本题需要用到欧拉函数。
题目显然是在求∑i=1n∑j=1n[gcd⁡(i,j)=1]\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1]i=1nj=1n[gcd(i,j)=1],推一下就可以发现答案为1+2∑i=2nφ(i)1+2\sum_{i=2}^n\varphi(i)1+2i=2nφ(i),线性筛求出欧拉函数然后求出这个式子就行了。
(然而我求稳写了O(nlog⁡log⁡n)O(n\log \log n)O(nloglogn)的筛法…)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,phi[500010],ans=0;
bool vis[500010]={0};

void calc_phi()
{
	for(int i=1;i<=n;i++) phi[i]=i;
	for(ll i=2;i<=n;i++)
		if (!vis[i])
		{
			for(ll j=1;i*j<=n;j++)
			{
				vis[i*j]=1;
				phi[i*j]=phi[i*j]*(i-1)/i;
			}
		}
}

int main()
{
	scanf("%lld",&n);
	calc_phi();
	for(int i=2;i<=n;i++) ans+=phi[i];
	ans=(ll)2*ans+1;
	printf("%lld",ans);
	
	return 0;
}

2017.10.28 Problem B
题目大意:nnn个炸弹,有些炸弹会连一条单向引线到另一个炸弹,引爆一个炸弹时,其引线连接的炸弹也会同时爆炸,引爆每个炸弹有一个非负分数,问引爆kkk个炸弹最多能获得的分数是多少。
做法: 本题需要用到缩环+贪心+堆。
注意到一点,如果我们把没引出引线的点看做向点000引了一条引线,那么整个图可以看做一棵树+若干棵环套树,注意到我们可以把环缩起来,并从缩起来的点向点000连边,那么可以构成一个等价的图,而这个图显然是一棵树。那么问题变成,选取kkk条到根的路径,使得这些路径所经过的点权之和最大。
然后我们就可以贪心了(我也不知道为什么能贪,写了个树形DP拍了几组数据发现结果都一样),先预处理出一个点子树中的点到该点能获得的最大点权和(也就是重链),还要记录提供最优答案的那个儿子(以下称为重儿子)。然后每次在堆里取最大的一个,然后顺着所取的路径,把路径上所有点的非重儿子的重链加入堆,取个kkk次之后就能得出答案了,时间复杂度为O(nlog⁡n)O(n\log n)O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int n,k,tot=0,first[400010]={0},vis[400010]={0},belong[200010],next[400010]={0};
ll a[200010],b[400010],f[400010],ans=0,mx,mxi;
struct edge {int v,next;} e[200010];
bool son[400010]={0};
struct point
{
	int id;
	ll val;
	bool operator < (point a) const
	{
		return val<a.val;
	}
};
priority_queue <point> Q;

ll read()
{
	ll s=0;
	char c;
	c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	{
		s=s*10+c-'0';
		c=getchar();
	}
	return s;
}

void find_loop(int v)
{
	if (v==0) return;
	if (!vis[a[v]]) vis[a[v]]=vis[v],find_loop(a[v]);
	else if (vis[a[v]]==vis[v])
	{
		ll i=a[v];
		++tot;b[tot]=0;
		while(i!=v)
		{
			belong[i]=tot;
			i=a[i];
		}
		belong[v]=tot;
	}
}

void insert(int a,int b)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	first[a]=tot;
}

void dfs(int v)
{
	f[v]=0;
	for(int i=first[v];i;i=e[i].next)
	{
		dfs(e[i].v);
		if (f[e[i].v]>f[v])
		{
			f[v]=f[e[i].v];
			next[v]=e[i].v;
		}
	}
	f[v]+=b[v];
}

int main()
{
	scanf("%d%d",&n,&k);
	belong[0]=0;
	for(int i=1;i<=n;i++)
	{
		a[i]=read(),b[i]=read();
		belong[i]=i;
	}
	
	tot=n;
	for(int i=1;i<=n;i++)
		if (!vis[i]) vis[i]=i,find_loop(i);
	tot=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
	{
		if (belong[i]>n) b[belong[i]]+=b[i];
		if (belong[i]==belong[a[i]]&&!vis[belong[i]]) vis[belong[i]]=1,insert(0,belong[i]);
		else if (belong[i]!=belong[a[i]]) insert(belong[a[i]],belong[i]);
	}
	
	dfs(0);
	point now,nex;
	now.id=0,now.val=f[0];
	Q.push(now);
	for(int i=1;i<=k;i++)
	{
		if (Q.empty()) break;
		now=Q.top(),Q.pop();
		ans+=now.val;
		int x=now.id;
		while(next[x])
		{
			for(int j=first[x];j;j=e[j].next)
				if (e[j].v!=next[x])
				{
					nex.id=e[j].v;
					nex.val=f[e[j].v];
					Q.push(nex);
				}
			x=next[x];
		}
	}
	printf("%lld",ans);
	
	return 0;
}

2017.10.28 Problem C
题目大意: 一个带边权的有向图,要从uuu点走到vvv点,,经过每个点要缴纳一个费用,每个点费用可能不同,一条路径的费用为路径上所有经过点的费用最大值,问在路径长度不超过sss的情况下,路径费用的最小值。
做法: 本题需要用到二分答案+最短路。
很显然需要二分费用,然后我们需要判定费用不超过一定值的时候存不存在长度不超过sss的路径,那么我们做一个最短路,不走费用超过该值的点即可。本题好像卡SPFA,所以我用Dijkstra算法写了,时间复杂度为O(nlog⁡n)O(n\log n)O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int n,m,u,v,tot=0,first[10010]={0};
ll inf,s,f[10010],mxf=0,dis[10010];
struct edge {int v,next;ll d;} e[100010];
struct point
{
	int id;
	ll val;
	bool operator < (point a) const
	{
		return val>a.val;
	}
};
bool vis[10010];
priority_queue <point> Q;

void insert(int a,int b,ll d)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	e[tot].d=d;
	first[a]=tot;
}

void dijkstra(ll limit)
{
	point now,next;
	while(!Q.empty()) Q.pop(); 
	for(int i=1;i<=n;i++)
	{
		if (i!=u) dis[i]=inf;
		else dis[i]=0;
	}
	if (f[u]>limit||f[v]>limit) return;
	
	for(int i=first[u];i;i=e[i].next)
		if (f[e[i].v]<=limit) dis[e[i].v]=min(dis[e[i].v],e[i].d);
	memset(vis,0,sizeof(vis));
	vis[u]=1;
	for(int i=1;i<=n;i++)
		if (i!=u&&f[i]<=limit)
		{
			now.id=i,now.val=dis[i];
			Q.push(now);
		}
	
	for(int i=2;i<=n;i++)
	{
		if (Q.empty()) return;
		now=Q.top(),Q.pop();
		while(vis[now.id])
		{
			if (Q.empty()) return;
			now=Q.top(),Q.pop();
		}
		vis[now.id]=1;
		for(int j=first[now.id];j;j=e[j].next)
			if (!vis[e[j].v]&&f[e[j].v]<=limit&&dis[e[j].v]>dis[now.id]+e[j].d)
			{
				next.id=e[j].v;
				next.val=dis[e[j].v]=dis[now.id]+e[j].d;
				Q.push(next);
			}
	}
}

int main()
{
	inf=1000000000;
	inf*=inf;
	
	scanf("%d%d%d%d%lld",&n,&m,&u,&v,&s);
	for(int i=1;i<=n;i++) scanf("%lld",&f[i]),mxf=max(mxf,f[i]);
	for(int i=1;i<=m;i++)
	{
		int a,b;ll d;
		scanf("%d%d%lld",&a,&b,&d);
		insert(a,b,d),insert(b,a,d);
	}
	
	dijkstra(inf);
	if (dis[v]>s) {printf("-1");return 0;}
	ll l=0,r=mxf;
	while(l<r)
	{
		ll mid=(l+r)/2;
		dijkstra(mid);
		if (dis[v]>s) l=mid+1;
		else r=mid;
	}
	printf("%lld",l);
	
	return 0;
}

2017.10.29 Problem A
题目大意: nnn支球队相互比赛,每两支球队会打一场主场和一场客场,赢了计333分,输了不计分,平局各计111分,给出比赛的情况,求出最后分数最高的球队编号(可能有多个)。
做法: 模拟,裸的不行,不讲了。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,mx=-1,score[110]={0},mxi[110],t=0;
char s[110];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		for(int j=1;j<=n;j++)
		{
			if (s[j-1]=='W') score[i]+=3;
			if (s[j-1]=='D') score[i]+=1,score[j]+=1;
			if (s[j-1]=='L') score[j]+=3;
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		if (score[i]==mx) mxi[++t]=i;
		if (score[i]>mx) mx=score[i],t=1,mxi[t]=i;
	}
	
	for(int i=1;i<=t;i++)
		printf("%d ",mxi[i]);
	
	return 0;
}

2017.10.29 Problem B
题目大意: 平面上有n(≤1000)n(\le 1000)n(1000)个点,记横坐标最小和最大的点为AAABBB,现在要从AAA走到BBB再走回AAA,并经过每个点一次且仅一次(AAA两次),并且从AAABBB走时只能向横坐标更大的点走,从BBBAAA走时只能向横坐标更小的点走,并且要求从AAABBB走时一定经过点b1b_1b1,从BBBAAA走时一定经过点b2b_2b2,问满足要求的最短路径长度。
做法: 本题需要用到DP。
转化为双线程DP,令f(i,j)f(i,j)f(i,j)为从AAA走到iii,从jjj走到AAA,且从AAAmax(i,j)max(i,j)max(i,j)都已经走过的最短路径长度,我们发现f(i,j)f(i,j)f(i,j)f(max⁡(i,j)+1,j)f(\max(i,j)+1,j)f(max(i,j)+1,j)f(i,max⁡(i,j)+1)f(i,\max(i,j)+1)f(i,max(i,j)+1)都可能有贡献,更新的式子为:
f(max⁡(i,j)+1,j)=min⁡(f(max⁡(i,j)+1,j),f(i,j)+dis(i,max(i,j+1)))f(\max(i,j)+1,j)=\min(f(\max(i,j)+1,j),f(i,j)+dis(i,max(i,j+1)))f(max(i,j)+1,j)=min(f(max(i,j)+1,j),f(i,j)+dis(i,max(i,j+1)))
以上式子当max⁡(i,j)+1≠b1\max(i,j)+1\ne b_1max(i,j)+1=b1时可以进行更新。
f(i,max⁡(i,j)+1)=min⁡(f(i,max⁡(i,j)+1),f(i,j)+dis(j,max(i,j+1)))f(i,\max(i,j)+1)=\min(f(i,\max(i,j)+1),f(i,j)+dis(j,max(i,j+1)))f(i,max(i,j)+1)=min(f(i,max(i,j)+1),f(i,j)+dis(j,max(i,j+1)))
以上式子当max⁡(i,j)+1≠b2\max(i,j)+1\ne b_2max(i,j)+1=b2时可以进行更新。
最后的答案为f(n,n)f(n,n)f(n,n),时间复杂度为O(n2)O(n^2)O(n2)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define inf 1000000000
using namespace std;
int n,b1,b2;
double x[1010],y[1010],dp[1010][1010];

double dis(int i,int j)
{
	return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}

int main()
{
	scanf("%d%d%d",&n,&b1,&b2);
	for(int i=0;i<n;i++)
		scanf("%lf%lf",&x[i],&y[i]);
	
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			dp[i][j]=inf;
	dp[0][0]=0;
			
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			if (i!=j||i==0)
			{
				int k=max(i,j)+1;
				if (i!=b1) dp[k][j]=min(dp[k][j],dp[i][j]+dis(i,k));
				if (j!=b2) dp[i][k]=min(dp[i][k],dp[i][j]+dis(j,k));
			}
	if (n-2!=b1) dp[n-1][n-1]=min(dp[n-1][n-1],dp[n-2][n-1]+dis(n-2,n-1));
	if (n-2!=b2) dp[n-1][n-1]=min(dp[n-1][n-1],dp[n-1][n-2]+dis(n-2,n-1));
	printf("%.2lf",dp[n-1][n-1]);
	
	return 0;
}

2017.10.29 Problem C
题目大意: 停车场有nnn个车位,有mmm个操作,每个操作是一辆车开入或者开出停车场,车开入之后会选择一个离最近的车最远的车位停入,如果有多个位置选择编号最小的,对于每个开入操作,输出它停放的车位编号。
做法: 本题需要用到链表+堆。
分析后发现,两个车位i,ji,ji,j之间停放车辆时,最远的距离应是⌊j−i2⌋−1\lfloor \frac{j-i}{2}\rfloor-12ji1(这里距离指两辆车之间相隔的车位数),这是一个定值,因此我们用一个链表维护车位被占用的情况,然后再用堆维护距离的最大值即可。时间复杂度O(mlog⁡m)O(m\log m)O(mlogm)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int n,m,pos[1000010],pre[500010],next[500010],val[500010],tot=2,nowtime=0;
int tim[500010]={0};
struct point
{
	int id,lft,val,t;
	bool operator < (point a) const
	{
		if (val!=a.val) return val<a.val;
		else return lft>a.lft;
	};
};
priority_queue <point> Q;

void insert(int x,int y)
{
	point now;
	val[++tot]=y;
	pre[next[x]]=tot;
	pre[tot]=x;
	next[tot]=next[x];
	next[x]=tot;
	now.id=x,now.lft=val[x],now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
	now.id=tot,now.lft=y,now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
}

void del(int x)
{
	point now;
	next[pre[x]]=next[x];
	pre[next[x]]=pre[x];
	now.id=pre[x],now.lft=val[pre[x]],now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
	tim[x]=nowtime;
}

int main()
{
	scanf("%d%d",&n,&m);
	pre[2]=1,next[1]=2;
	val[1]=0,val[2]=n+1;
	point now;
	now.id=1,now.lft=0,now.val=n+1,now.t=0;
	Q.push(now);
	for(int i=1;i<=m;i++)
	{
		int op,x,ans;
		scanf("%d%d",&op,&x);
		if (op==1)
		{
			now=Q.top(),Q.pop();
			while(now.t<tim[now.id]||val[now.id]+1==val[next[now.id]]) now=Q.top(),Q.pop();
			if (now.id==1) ans=1;
			else if (next[now.id]==2) ans=n;
					else ans=now.lft+now.val+1;
			insert(now.id,ans);
			pos[x]=tot;
			printf("%d\n",ans);
		}
		if (op==2) del(pos[x]);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值