【問題描述】
定義集合S的價值D(S)爲:
現在給你n個元素,並給出其中任意兩個元素之間的d(i,j)值,要你將這些元素劃分成兩個集合A、B。求min{D(A)+D(B)}。注:d(i,j)=d(j,i)。
【輸入格式】
輸入數據的第一行是一個整數n,代表元素個數。
之後n-1行描述的是d(i,j),這部分裏,第i行包含n-i個整數,第i行第j列的整數代表的是d(i,i+j)。
【輸出格式】
輸出只有一行,一個整數,代表min{D(A)+D(B)}。
【輸入樣例】
【樣例1】
5
4 5 0 2
1 3 7
2 0
4
【樣例2】
7
1 10 5 5 5 5
5 10 5 5 5
100 100 5 5
10 5 5
98 99
3
【輸出樣例】
【樣例1】
4
【樣例2】
15
【數據範圍】
1 ≤ n ≤ 200
0<=d(i,i+j)<=10^9
——————————————————————————————————————————————————————
想到可以用2_SAT來判定,但是這是判定,關鍵是主程序的框架怎麼寫。
肯定要先把所有的邊拿出來排序搞事情這個沒話說的。
想了很久二分套二分,發現一點道理都沒有。。。。
後來又想枚舉一邊二分另一邊,嗯似乎很有道理的樣子,但是仔細一想不對要TL。。。。(我家題庫上時限1S簡直。。QAQ)
已經發現可以枚舉一邊二分另一邊了,那麼就考慮一下維護兩個單調指針。這個指針代表的是當前情況下兩個集合中各自最大的邊的權值。然後在2_SAT中把一個點拆成2個分別表示在第一個集合(一下簡稱0)和在第二個集合(以下簡稱1)的情況,然後就開始考慮建立2 _SAT的判定圖。(可以證明這是滿足單調性的,但是有一點複雜,主要是考慮一些特殊情況以及假設驗證,同時2 _SAT判定到假解的影響是會被消除的,證了兩節晚自習。。。)
左指針(以下簡稱L)右邊的所有邊連接的點都要連邊0->1,右指針(以下簡稱R)右邊的邊連接的點都要連邊1->0,然後在左右指針向右向左走的過程中就會出現L經過的地方刪邊,R經過的地方要加邊,所以要動態維護(打標記會TL的很慘ORZ)。
但是這樣還是會TL,要加一個剪枝,發現加的邊連接的點在上一次判定成功的圖中全都在不同集合中的時候就可以不用重新判定了。
然後就可以1S過了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 2*1e9+5
using namespace std;
const int maxn=205;
const int maxm=40005;
int N,g[maxn][maxn];
int ans=inf;
bool vis[maxn<<1];
struct _edge{ int from,to,w; }_E[maxm];
struct edge{ int to,next; }E[maxm<<1];
int first[maxn<<1],np,cnt,other[maxn<<1];
int stk[maxn<<1],top;
void data_in()
{
scanf("%d",&N);
int x;
for(int i=1;i<N;i++)
for(int j=i+1;j<=N;j++)
{
scanf("%d",&x);
g[i][j]=g[j][i]=x;
}
}
bool cmp(_edge x,_edge y) { return x.w<y.w; }
void add_edge(int u,int v)
{
E[++np]=(edge){v,first[u]};
first[u]=np;
}
void delete_edge(int u,int v)
{
int p=first[u],lastp=0;
while(p)
{
if(E[p].to==v) break;
lastp=p;
p=E[p].next;
}
if(lastp) E[lastp].next=E[p].next;
else first[u]=E[p].next;
}
void init()
{
for(int i=1;i<N;i++)
for(int j=i+1;j<=N;j++)
_E[++cnt]=(_edge){i,j,g[i][j]};
sort(_E+1,_E+cnt+1,cmp);
for(int i=1;i<=N;i++) other[i]=i+N,other[i+N]=i;
for(int i=1;i<=cnt;i++)
{
int x=_E[i].from,y=_E[i].to;
add_edge(x,other[y]);
add_edge(y,other[x]);
}
}
bool DFS(int i)
{
vis[i]=1;
stk[++top]=i;
for(int p=first[i];p;p=E[p].next)
{
int j=E[p].to;
if(vis[j]) continue;
if(vis[other[j]]) return 0;
if(!DFS(j)) return 0;
}
return 1;
}
bool two_SAT()
{
memset(vis,0,sizeof(vis));
for(int i=1;i<=N;i++) if(!vis[i]&&!vis[other[i]])
{
top=0;
if(!DFS(i))
{
while(top) vis[stk[top--]]=0;
if(!DFS(other[i])) return 0;
}
}
return 1;
}
void work100()
{
init();
int L=0,R=cnt;
bool ok=0;
while(L<=R)
{
if(ok||two_SAT())
{
ok=1;
ans=min(ans,_E[L].w+_E[R].w);
add_edge(other[_E[R].from],_E[R].to);
add_edge(other[_E[R].to],_E[R].from);
if(!(vis[_E[R].from]&&vis[other[_E[R].to]]
||vis[_E[R].to]&&vis[other[_E[R].from]])) ok=0;
R--;
while(R>L&&_E[R].w==_E[R+1].w)
{
add_edge(other[_E[R].from],_E[R].to);
add_edge(other[_E[R].to],_E[R].from);
if(!(vis[_E[R].from]&&vis[other[_E[R].to]]
||vis[_E[R].to]&&vis[other[_E[R].from]])) ok=0;
R--;
}
}
else
{
ok=0;
L++;
delete_edge(_E[L].from,other[_E[L].to]);
delete_edge(_E[L].to,other[_E[L].from]);
while(L<R&&_E[L].w==_E[L+1].w)
{
L++;
delete_edge(_E[L].from,other[_E[L].to]);
delete_edge(_E[L].to,other[_E[L].from]);
}
}
}
if(N==1||N==2) ans=0;
printf("%d\n",ans);
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
data_in();
work100();
return 0;
}