题解:P6898 [ICPC 2014 WF] Metal Processing Plant
前言
来自山西队爷的分享题。
思路分析
首先有一个朴素的想法,能否枚举两个集合的边权,判断是否合法。
发现是可以的。
具体地,我们设 \(D(A)=w_1,D(B)=w_2\),不失一般性,假设 \(w_1 \ge w_2\)。
这样,我们可以把图上的边 \((x,y,w)\) 分成三类:
-
\(w \le w_2\):没有影响;
-
\(w2<w\le w_1\):\(x,y\) 不能同时在 \(B\) 集合中;
-
\(w>w_1\):\(x,y\) 不能同时在一个集合中。
发现限制很像一个 2-sat 问题,考虑将 \(x\) 拆成 \(x',x\),分别表示在 \(A,B\) 集合中。
-
对于不能同时在 \(B\) 集合的点,\(x\to y'\),\(y \to x'\);
-
对于不能同时在一个集合中的点,\(x\to y'\),\(y\to x'\),\(x' \to y\),\(y' \to x\)。
然后直接 tarjan 判定是否合法。
复杂度 \(O(n^6)\)。
能不能再给力一点啊?
大胆猜测单调性,也就是 \(w_1\) 递增时,合法的 \(w_2\) 递减,这样可以双指针维护。
复杂度 \(O(n^4)\)。
能不能再给力一点啊?
考虑对于不能在同一个集合的限制,实际上也太严苛了。
考虑从大到小枚举 \(w_1\),二分求出 \(w_2\) 的过程中,我们在线维护 \(w \ge w_1\) 的边的连通性,如果当前边连上出现环,分类讨论:
-
如果是奇环,那么以后所有的边都加不进去了,考虑做完这次直接终止;
-
如果是偶环,那么图的联通性并没有改变,二分出的 \(w_2\) 结果不会改变,直接更新答案就行。
判断环的奇偶性可以带权并查集。
分析我们的复杂度,我们本质不同的 \(w_1\) 只有 \(O(n)\) 个。
复杂度 \(O(n^3 \log n)\),常数不大,可以通过。
代码实现
要特别注意 \(0\) 的问题。
#include<bits/stdc++.h>
using namespace std;
const int inf=2e9+10;
int n,m,cnt,p,ans,v;
struct node{
int x,y,w;
}h[40005];
bool cmp(node a,node b){
return a.w<b.w;
}
int fa[205],w[205];
void init(){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
int find(int x){
if(fa[x]==x) return x;
int y=find(fa[x]);
w[x]^=w[fa[x]];
fa[x]=y;
return y;
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
fa[fy]=fx;
w[fy]=w[x]^w[y]^1;
}
int head[405],nxt[400005],target[400005],tot;
void add(int x,int y){
tot++;
nxt[tot]=head[x];
head[x]=tot;
target[tot]=y;
}
int dfn[405],low[405],vis[405],col[405],dcnt,ccnt;
stack<int> s;
void tarjan(int x){
dfn[x]=low[x]=++dcnt;
vis[x]=1;
s.push(x);
for(int i=head[x];i;i=nxt[i]){
int y=target[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}else if(vis[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
ccnt++;
col[x]=ccnt;
vis[x]=0;
while(s.top()!=x){
int y=s.top();
s.pop();
col[y]=ccnt;
vis[y]=0;
}
s.pop();
}
}
bool check(int w1,int w2){
for(int i=1;i<=(n<<1);i++){
head[i]=dfn[i]=low[i]=vis[i]=col[i]=0;
}
dcnt=ccnt=tot=0;
for(int i=1;i<=cnt;i++){
if(h[i].w<=w2){
continue;
}else if(h[i].w>w1){
add(h[i].x,h[i].y+n);
add(h[i].y+n,h[i].x);
add(h[i].y,h[i].x+n);
add(h[i].x+n,h[i].y);
}else{
add(h[i].x,h[i].y+n);
add(h[i].y,h[i].x+n);
}
}
for(int i=1;i<=(n<<1);i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++){
if(col[i]==col[i+n]) return false;
}
return true;
}
int find(int l,int r,int w){
if(l==r){
if(check(w,h[l].w)) return l;
else return -1;
}
int mid=(l+r)>>1;
if(check(w,h[mid].w)) return find(l,mid,w);
else return find(mid+1,r,w);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
cin>>v;
h[++cnt]=(node){i,j,v};
}
}
sort(h+1,h+1+cnt,cmp);
if(check(0,0)){
cout<<0;
return 0;
}
p=0,ans=inf;
init();
for(int i=cnt;i>=1;i--){
if(find(h[i].x)==find(h[i].y)){
if(w[h[i].x]==w[h[i].y]){
p=find(p,cnt,h[i].w);
if(p!=-1) ans=min(ans,h[i].w+h[p].w);
break;
}else ans=min(ans,h[i].w+h[p].w);
}else{
merge(h[i].x,h[i].y);
p=find(p,cnt,h[i].w);
if(p==-1) break;
ans=min(ans,h[i].w+h[p].w);
}
}
cout<<ans;
return 0;
}