题目:CF316B2 EKG
拓扑排序 - 01背包 - DP - 图论
双倍经验:
CF316B1 EKG
CF316B2 EKG
仔细观察题目,我们发现给出的队列信息形成了若干条链。而最终的完整队列是由这些小的链组成的,且队内顺序不能交换
想要找出所有 p o s pos pos 可能站的位置,我们就需要找出站在他前面所有可能的链的组合(前面人的个数)
首先,我们肯定能想到暴力枚举,时间复杂度
O
(
2
n
)
O(2^n)
O(2n),显然不行
仔细撕烤发现,我们只需要知道
1
∼
n
1\sim n
1∼n 中,那些值能被若干条链长总和凑出来,并不需要知道怎么凑出来的
也许你已经发现,这跟01背包的用途非常像
我们只需要将每一个链长(除了 p o s pos pos 所在的链)当成一个物品,跑一边01背包就可以了,时间复杂度 O ( n 2 ) O(n^2) O(n2)
最后别忘了,我们是要求 p o s pos pos 前面的人数,所以需要求出 p o s pos pos 所在的小链中排第几个,我们设这个值为 v a l val val。对于每一个能被其他若干条链长凑出的值 x x x,我们输出 x + v a l x+val x+val
AC代码
#include<cstdio>
#include<iostream>
using namespace std;
const int Maxn=1000+20,inf=0x3f3f3f3f;
int nxt[Maxn],a[Maxn];
bool f[Maxn],vis[Maxn];
int n,m,val,pos;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
int main()
{
// freopen("in.txt","r",stdin);
n=read(),pos=read();
for(int i=1;i<=n;++i)
{
int x=read();
if(x)nxt[x]=i,vis[i]=1;
}
for(int i=1;i<=n;++i)
{
if(vis[i])continue;
int cnt=0,x=i;
while(x)
{
++cnt;
if(x==pos){val=cnt;goto GG;}
x=nxt[x];
}
a[++m]=cnt;
GG:;
}
f[0]=1;
for(int i=1;i<=m;++i)
{
for(int j=n;j>=a[i];--j)
if(f[j-a[i]])f[j]=1;
}
for(int i=0;i<=n;++i)
if(f[i])printf("%d\n",i+val);
return 0;
}