P9571 Horizon Blue

发布时间 2023-08-22 19:38:57作者: One_JuRuo

思路

首先分析一下操作 \(2,3\)

对于操作 \(2\),容易发现如果 \(k\) 相等,就只可能是平行或者重合,显然不满足,那么答案就是总剩余直线数减去 \(k\) 相同的直线数。

对于操作 \(3\),发现只有平行的直线不会被删去,也就是只有 \(k\) 相同而 \(b\) 不同的直线不会被删去。

如此一来,这道题核心做法就很明显了,但是具体实现有点麻烦。

首先,我想到的是拿个 map 存,但是很快发现这样做,前两个操作还好,但是对于操作 \(3\) 的删除,实在是太慢了,会 TLE。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,op,k,b,sum;
map<pair<int,int>,int>m;
map<int,int>m1;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d%d",&op,&k,&b);
		if(op==1) m[make_pair(k,b)]++,m1[k]++,sum++;//统计这个k,b出现的次数和k出现的次数以及总直线数
		else if(op==2) printf("%d\n",sum-m1[k]);
		else
		{
			int t=m1[k]-m[make_pair(k,b)];
			m1.clear(),sum=m1[k]=t;//只有同k不同b的能留下来
			for(auto j:m) if(j.first.first!=k||j.first.second==b) m[j.first]=0;//m中存的只能挨个遍历删除了
		}
	}
	return 0;
}

已提交,果然 TLE 了两个点,但是一看 subtask 5 也有个点 TLE 了,这代表什么?这代表我们可以用特殊性质偷奸耍滑多拿一点部分分。

subtask 5 的特殊性质是第 \(i\) 此操作 \(k=i\)

这样显然没有直线会 \(k\) 相同,所以每次删除都必然全部删完,就不用去遍历了。

但是我们怎么直到这个是 subtask 5 的数据了,我们只需要每次判断是否 \(k=i\),如果都是的话,就特殊处理,这样的话我们就可以多获得 \(14\) 分了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,op,k,b,sum,flag;
map<pair<int,int>,int>m;
map<int,int>m1;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d%d",&op,&k,&b),flag=(k==i&&!flag)?0:1;//判断是否满足特殊性质
		if(op==1) m[make_pair(k,b)]++,m1[k]++,sum++;
		else if(op==2) printf("%d\n",sum-m1[k]);
		else
		{
			if(!flag) m.clear(),m1.clear(),sum=0;//满足就特殊处理
			else
			{
				int t=m1[k]-m[make_pair(k,b)];
				m1.clear(),sum=m1[k]=t;
				for(auto j:m) if(j.first.first!=k||j.first.second==b) m[j.first]=0;
			}
		}
	}
	return 0;
}

但是最后一个点 TLE 了,这种办法实在是无能为力了,\(75\) 分大概就是极限了。

这样的话,我们就需要改改思路了。

显然,这个方法最慢的就是删除了,那么我们可以考虑延迟删除操作,直到每次需要调用或者修改的时候再进行删除。

我们可以用一个数组存它们进行了第几轮的删除操作,再拿个变量储存目前是第几轮删除操作,如果调用这个 \(k\) 的数据,就判断记录的是否等于目前的删除操作次数。

这样的话时间复杂度一下子就降下来了,但是还有个问题就是 \(k\) 相同 \(b\) 相同的直线如何删除。

如果还是用 map 来存的话,一定没办法快速删除,所以我们只能换一种储存方式。

首先想到 multiset,这是一个集合,但是允许相同元素出现,我们只需要直到它的一个功能 erase 就行。

它可以把这个集合所有等于参数的数全部删除,如此一来,我们成功地把时间复杂度降下来了。

剩下的,就是敲代码拿下 AC 了!

AC 代码

#include<bits/stdc++.h>
using namespace std;
int n,op,k,b,num[200005],sum,dfn[200005],cnt;
multiset<int>s[200005];
int main()
{
    scanf("%d",&n);
	while(n--)
	{
        scanf("%d%d%d",&op,&k,&b);
        if(k<0) k+=200001;//因为用了数组,所以需要把负值k板正,只要不重复就好,所以就加成正数好了。
        if(dfn[k]!=cnt) dfn[k]=cnt,num[k]=0,s[k].clear();//判断当前k有没有执行删除操作
		if(op==1) ++num[k],++sum,s[k].insert(b);//记录
		else if(op==2) printf("%d\n",sum-num[k]);
		else dfn[k]=++cnt,num[k]-=s[k].count(b),s[k].erase(b),sum=num[k];//删除
	}
	return 0;
}