P2058

发布时间 2023-10-20 20:08:00作者: Kai-G

这道不难的题引发了我不少思考
我第一个版本是用vector嵌套vector写成的,后来发现没必要还存储那些已经超过24h的船,完全可以删除前面的船,因此把外层vector换成了deque。
即用deque存船,用vector存储船上人的国籍
然后喜提TLE,只有70分
然后把cincout换成scanfprintf,仍然TLE
把scanfprintf换成快读快写函数,仍然TLE
意识到可以把存船换成存人,遂把deque换成queue,把存了一个int时间和一个vector国籍的船类换成的只存int时间和int国籍的人类,这样deque套vector直接变成了单单一个queue,但仍然TLE
(后来意识到这三个优化全属于小打小闹,意义不大,第三个优化有点意义,但时间提升其实也不大,还多浪费了一点空间)

然后意识到我应该分析循环次数,发现外层循环循环了n次,内层循环了Ki次+2100000。n是小于100000的数,每个n总要处理不可能缩小,可是n2100000显然达到了十的十次方超时间复杂度了。内层循环的Ki一共运行了sigmaKi次,而sigmaKi是小于300000的,最开始我看错了,我以为Ki小于300000,Ki缩小不了所以才用了个2100000级别的内层运算。结果发现Ki的和才300000次,因而很明显缩小的重点就在于这个两个100000的循环了

这段分析非常重要,让我突然一直到该优化哪里,此前优化的地方都是小打小闹,要么是常数级的优化,要么是至多1~2个数量级的优化,这段分析才真正找到了症结所在。
这种分析我已经好久没做忘却了,这道题让我拾起来这种记忆。给了我三个收获,一是要认真读题,别漏看了sigma,二是写算法前先算好自己的内层循环一共会被执行多少次。三是算法TLE了先检查循环次数是否有数量级级别的大问题,没有的话在进行常数级的小优化。

为什么有两个100000级别的循环呢,因为我用b[i]存储i国籍的人有没有,我用1和0两个状态来存储有没有,而不是用数字存储有多少,这导致我让24h以前的人出队只能将b数组全部清零然后全部新统计,而不能直接在原本b数组的基础上修修改改,因为我不知道出队后b[i]==1的数据该不该归零,毕竟24h以前的人中有没有那个国籍的人我不知道。那么,只要改存数字,把粗暴地损失了信息的b[i]=1改成b[i]++,就可以存储剩下的人了,就可以让那些人出队了,出队后只要b[i]--;我就可以判断那个国籍的人到底还有没有,从而可以直接在b数组上修修改改了,就不需要再全部清零再全部重新统计了。这样就把十的五次方级别的操作直接改成了常数级别的操作。
然后另一个100000级别的循环是我每次处理一艘船的答案的时候都是直接设置一个新的ans量并初始化为0,然后遍历b数组。可是,ans也可以略加修改变成只在原本的基础上修修改改,从而直接把遍历b数组这种是的五次方级别的操作改成了常数级别的操作。
就这样AC了。我看题解中很多人都强调要按人头来存,但根据我的分析很明显我这个算法按船来存按人头来存都是不会TLE的,因为我用船来存的时候用了vector。

Code-1

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <map>
#include <vector>
#include <queue>
#include <list>
using namespace std;


inline int read(){
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
inline void write(int x){ 
    if(x==0){putchar('0');return;}
	int len=0,k1=x,c[10005];
	if(k1<0)k1=-k1,putchar('-');
	while(k1)c[len++]=k1%10+'0',k1/=10;
	while(len--)putchar(c[len]);
}

struct ship
{
	int t;
	vector<int> na;
	ship(int T,vector<int> NA){t=T;na.assign(NA.begin(),NA.end());}
};
queue<ship ,list< ship > > Q;
int b[100005],n,t,k,ana;
int main()
{
	Q.push(ship(0,vector<int>()));
	n=read();
	for(int i=1;i<=n;i++)
	{
		t=read();k=read();
		vector<int> tna;
		for(int j=1;j<=k;j++)
		{
			ana=read();
			tna.push_back(ana);
		}
		for(Q.push(ship(t,tna));Q.back().t-Q.front().t>=86400,Q.pop();)
			for(auto p=Q.front().na.begin();p<Q.front().na.end();p++)
				b[*p]--;
		for(auto p=Q.back().na.begin();p<Q.back().na.end();p++)
				b[*p]++;
		int ans=0;
		for(int j=1;j<=100000;j++)if(b[j]>0)ans++;
		write(ans);putchar('\n');
	}    
	return 0;
}

Code-2

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <map>
#include <vector>
#include <queue>
#include <list>
using namespace std;


inline int read(){
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
inline void write(int x){ 
    if(x==0){putchar('0');return;}
	int len=0,k1=x,c[10005];
	if(k1<0)k1=-k1,putchar('-');
	while(k1)c[len++]=k1%10+'0',k1/=10;
	while(len--)putchar(c[len]);
}

struct p
{
	int t,na;
	p(int T,int NA){t=T;na=NA;}
};
queue<p> Q;
int b[100005],n,t,k,ana;
int main()
{
	Q.push(p(0,0));
	n=read();
	for(int i=1;i<=n;i++)
	{
		t=read();k=read();
		for(int j=1;j<=k;j++)
		{
			ana=read();b[ana]++;p h=Q.front();
			for(Q.push(p(t,ana));t-h.t>=86400;Q.pop())
				b[Q.front().na]--;
		}
		int ans=0;
		for(int j=1;j<=100000;j++)if(b[j]>0)ans++;
		write(ans);putchar('\n');
	}    
	return 0;
}

Code3

#include <iostream>
#include <queue>
using namespace std;
struct p{int t,na; p(int T,int NA){t=T;na=NA;} };
int b[100005],n,t,k,ana,ans;
queue<p> Q;
int main()
{
	Q.push(p(0,0));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&t,&k);
		for(int j=1;j<=k;j++)
		{
			scanf("%d",&ana);
			b[ana]++;
			if(b[ana]==1)ans++;
			for(Q.push(p(t,ana));t-Q.front().t>=86400&&!Q.empty();Q.pop())
			{	
				b[Q.front().na]--;
				if(b[Q.front().na]==0)ans--;
			}
		}
		printf("%d\n",ans);
	}  
	return 0;
}