【NOIP2017提高A组模拟9.23】碎

Description

给出n个点,你需要把这n个点分成两个块,如果点i和j分在了同一个块那么会产生d[i][j]的代价,定义一个块的代价这个块内的所有代价的最大值。现在你需要使两个块的代价和最小。
n<=300

Solution

谴责出题人,暴力竟然能过
首先我们考虑一个多项式算法。
设D(A)为第一个块的代价,D(B)为第二个块的代价,我们强制D(A)>=D(B)
那么枚举D(A),二分D(B),然后对于每条边相当于一个限制
这就是经典2-sat判断是否存在解的问题
复杂度O(N^4 log N)
接下来我们考虑减少D(A)的取值数量
对于原图中的一个偶环,环中的最小边不可能成为D(A)的取值
对于原图中的一个奇环,环中的最小边会成为D(A)取值的下界
这个自己把图画出来就知道了
那么我们就把所有边排序,从大到小加入,用带权并查集维护环的奇偶性,如果出现奇环就退出
这样我们D(A)的取值就变成了N种
复杂度O(N^3 log N)

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
using namespace std;
const int N=305,M=N*N,inf=0x7fffffff;
struct E{int v,x,y;}a[M];
bool cmp(E x,E y) {return x.v>y.v;}
int n,fa[N],len[N],bl[N],b[N],ans,tot;
int stack[N],dfn[N],low[N],cnt,col;
int t[M],next[M],last[N],l;
bool in[N],vis[N];
void add(int x,int y) {
    t[++l]=y;next[l]=last[x];last[x]=l;
}
void add_xor(int x,int y) {
    int nowx=x*2-1,nowy=y*2-1;
    add(nowx,nowy^1);add(nowy,nowx^1);
    add(nowx^1,nowy);add(nowy^1,nowx);
}
void add_and(int x,int y) {
    int nowx=x*2-1,nowy=y*2-1;
    add(nowx^1,nowy);
    add(nowy^1,nowx);
}
void tarjan(int x) {
    dfn[x]=low[x]=++cnt;in[x]=1;stack[cnt]=x;vis[x]=1;
    rep(i,x)
        if (!vis[t[i]]) {
            tarjan(t[i]);
            low[x]=min(low[x],low[t[i]]);
        } else if (in[t[i]]) low[x]=min(low[x],dfn[t[i]]);
    if (dfn[x]==low[x]) {
        col++;
        while (stack[cnt+1]!=x) {
            bl[stack[cnt]]=col;
            in[stack[cnt]]=0;
            cnt--;
        }
    }
}
bool solve(int da,int db) {
    memset(last,0,sizeof(last));l=0;
    fo(i,1,tot)
        if (a[i].v>da) add_xor(a[i].x,a[i].y);
        else if (a[i].v>db) add_and(a[i].x,a[i].y);
    memset(vis,0,sizeof(vis));
    memset(bl,0,sizeof(bl));
    col=cnt=0;
    fo(i,0,n*2-1)
        if (!vis[i]) tarjan(i);
    fo(i,0,n*2-1) 
        if (bl[i]==bl[i^1]) return 0;
    return 1;
}
int get(int x) {
    if (!fa[x]) return x;
    int y=get(fa[x]);
    len[x]^=len[fa[x]];
    return fa[x]=y;
}
void merge(int ax,int by,int x,int y) {
    if (ax>by) swap(ax,by);
    fa[by]=ax;len[by]=len[x]^len[y]^1;
}
int main() {
    freopen("broken.in","r",stdin);
    freopen("broken.out","w",stdout);
    scanf("%d",&n);ans=inf;
    fo(i,1,n-1)
        fo(j,i+1,n) {
            ++tot;
            scanf("%d",&a[tot].v);
            a[tot].x=i;a[tot].y=j;
        }
    sort(a+1,a+tot+1,cmp);
    fo(i,1,tot) {
        int x=a[i].x,y=a[i].y;
        int ax=get(x),by=get(y);
        if (ax==by) {
            if (len[x]^len[y]) continue;
            else {b[++b[0]]=a[i].v;break;}
        } else merge(ax,by,x,y),b[++b[0]]=a[i].v;
    }
    fo(i,1,b[0]) {
        int l=0,r=b[i];
        while (l<r) {
            int mid=(l+r)/2;
            if (solve(b[i],mid)) r=mid;
            else l=mid+1;
            if (l+b[i]>=ans) break;
        }
        if (!solve(b[i],l)) continue;
        ans=min(ans,b[i]+l);
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值