[NOI2021SDPT2Test2]多項式時間哈密頓迴路(二分答案+tarjan縮點+拓撲排序)
阿新 • • 發佈:2021-10-15
二分、圖論、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); }