1. 程式人生 > 其它 >[NOI2021SDPT2Test2]多項式時間哈密頓迴路(二分答案+tarjan縮點+拓撲排序)

[NOI2021SDPT2Test2]多項式時間哈密頓迴路(二分答案+tarjan縮點+拓撲排序)

二分、圖論、dp的綜合運用。

“要求讓最少個數的一種 'QwQ' 的個數最多的方案”,顯然可知主體演算法為二分答案,考慮 check() 怎樣實現。

“保證對於每個 \(x\),最多有一個 \(a\) 使得 \(a\to x\) 成立”,故此轉化關係可以抽象為樹或基環樹。儘管不保證連通,但我們可以建出超級源點,連線整個森林。對於樹的情況,考慮從葉子向根推,計算根節點需要多少額外轉化使其合法,若 \(dp_{root} \le m\) 則可行。這一過程可以用 dfs 回溯或拓撲排序完成。對於基環樹,只需要縮環為點——tarjan 等求強連通分量即可。

程式碼複雜,可以使用 namespace 簡化,降低除錯難度。

下面是 AC 程式碼:

#include<cstdio>
#include<queue>

inline int min(const int& x,const int& y){return x<y?x:y;}

inline int rd()
{
	int x=0,f=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) f^=(c=='-');
	for(;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	return f?x:-x;
}

const int N=1e6+10;
int n,m,fa[N],tmp[N],a[N],ind[N];
int tot,h[N],ver[N],nxt[N];
int scc,c[N],siz[N];//siz[]記錄環中的點數,計算花費
long long val[N],f[N];//val[]計算縮點的點權和
std::queue<int> q;

namespace Tarjan
{
	int dfs_clock,dfn[N],low[N];
	int tot,h[N],ver[N],nxt[N];
	bool vis[N];
	int st[N],top;
	
	inline void add(int u,int v)
	{
		nxt[++tot]=h[u];
		ver[tot]=v;
		h[u]=tot;
	}
	
	inline void tarjan(int u)
	{
		dfn[u]=low[u]=++dfs_clock;
		st[++top]=u,vis[u]=true;
		for(int i=h[u];i;i=nxt[i])
		{
			int v=ver[i];
			if(!dfn[v])
			{
				tarjan(v);
				low[v]=min(low[u],low[v]);
			}
			else if(vis[v]) low[u]=min(low[u],dfn[v]);
		}
		if(dfn[u]==low[u])
		{
			int x=st[top--];
			c[x]=++scc,siz[scc]=1,val[scc]+=a[x],vis[x]=false;
			while(u!=x)
			{
				x=st[top--];
				c[x]=scc,++siz[scc],val[scc]+=a[x],vis[x]=false;
			}
		}
	}
};

inline void add(int u,int v)
{
	nxt[++tot]=h[u];
	ver[tot]=v;
	h[u]=tot;
}

inline bool check(long long k)
{
	q.push(0);//注意加入超級源點
	for(int i=1;i<=scc;++i)
	{
		f[i]=val[i];
		ind[i]=tmp[i];
		if(!ind[i]) q.push(i);
	}
	long long sum=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=h[u];i;i=nxt[i])
		{
			int v=ver[i];
			if(f[u]<k*siz[u])
			{
				f[v]-=k*siz[u]-f[u];
				f[u]=k*siz[u];
			}
			--ind[v];
			if(!ind[v]) q.push(v);
		}
		if(f[u]<k*siz[u]) sum+=k*siz[u]-f[u];
		if(sum>m) return 0;
	}
	return sum<=m;
}

int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=n;++i)
	{
		fa[i]=rd();
		if(fa[i]!=-1&&fa[i]!=i) Tarjan::add(i,fa[i]);
	}
	for(int i=1;i<=n;++i) a[i]=rd();
	for(int i=1;i<=n;++i)
		if(!Tarjan::dfn[i]) Tarjan::tarjan(i);
	for(int i=1;i<=n;++i)
	{
		if(fa[i]!=-1&&fa[i]!=i)
			add(c[i],c[fa[i]]),++tmp[c[fa[i]]];
	}
	long long l=1e8,r=0;
	for(int i=1;i<=n;++i)
	{
		l=min(l,a[i]);
		r+=a[i];
	}
	r=(r+m)/n;
	while(l<r)
	{
		long long mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	printf("%lld\n",l);
}

THE END