设\(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;
}