洛谷P7568

\(f[i][j]\)表示在第\(i\)时刻之前,第\(i-1\)时刻之后(即在两者之间)追杀第\(j\)个人,最后剩下的总人数。
\(life[i][j]\)表示第\(i\)个人在第\(j\)时刻的任务\((u,v)\)执行之前剩余的生命值
如果\(j≠u[i-1]\)
或者说
如果\(j==u[i-1]\)\(life[u[i-1]][i-1]==0\)
或者说
如果\(j==u[i-1]\)\(life[u[i-1]][i-1]≥2\)
或者说
如果\(j==u[i-1]\)\(life[u[i-1]][i-1]==1且life[v[i-1]][i-1]==0\)
那么\(f[i][j]=f[i-1][j]\)
这几个方程都比较显然,请看清我们定的状态的表示的意义(特别是加黑的字体)后认真思考。
除了这几种情况,我们才需要进行简单模拟。
模拟一次的时间复杂度显然是\(O(n)\)
设当前时刻的上一个时刻的任务为\((u,v)\),由以上分析可知,此时\(u\)\(v\)的生命值分别是\(1\)和不为\(0\)
那么在经过这个任务后,所有人的总生命值就会减\(1\)
显然总生命值为\(3m\)
那么这个模拟的过程最多有\(3m\)次。
所有总的时间复杂度为\(O(nm)\)
滚动数组优化即可。
code有一些细节,详情见代码。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=6e4+10,M=1010;
int n,m;
int u[N],v[N],f[M],life[M],ans[M],a[M];
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-48;s=getchar();}
    return x*f;
}
void attack(int time,int x)
{
	for(int i=1;i<=m;i++)
	a[i]=life[i];
	a[v[time-1]]--;//注意这一行,因为我们的状态定义为上一个任务还没有执行,所以在模拟的时候要先把上一个任务执行了来 
	if(a[x])
	a[x]--;
	for(int i=time;i<=n;i++)
	if(a[u[i]]&&a[v[i]]) a[v[i]]--;
	int sum=0;
	for(int i=1;i<=m;i++) 
	if(a[i]) sum++;
	f[x]=sum;
}
int main() 
{
	n=read(),m=read();
	for(int i=1;i<=n;i++)
	u[i]=read(),v[i]=read();
	for(int i=1;i<=m;i++) life[i]=3;
	for(int i=1;i<=m;i++)
	attack(1,i);//预处理在1时刻之前杀每一个人 
	for(int i=1;i<=m;i++)
	ans[f[i]]++;//先统计答案 
	for(int i=2;i<=n+1;i++)//枚举的当前时刻。由状态定义可知我们需要上一个时刻的任务。所以下面的代码i-1 
	{
		for(int j=1;j<=m;j++)
		if(j==u[i-1]&&life[u[i-1]]==1&&life[v[i-1]])
		attack(i,j);//只有这一种情况需要模拟 
		for(int j=1;j<=m;j++)
		ans[f[j]]++;//统计答案 
		if(life[u[i-1]]&&life[v[i-1]]) life[v[i-1]]--;//更新生命值 
	}
	for(int i=0;i<=m;i++)
	printf("%d ",ans[i]);
    return 0;
}
posted @ 2021-09-12 19:52  最爱丁珰  阅读(56)  评论(0编辑  收藏  举报