[Wf2014]Metal Processing Plant(金属加工厂) 之这是小少主我用二分答案用得最6的一次(所有数据共1S)
来源:互联网 发布:诛仙数据管理工具 编辑:程序博客网 时间:2024/04/28 21:06
题目
【问题描述】
定义集合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
分析
这道题目我们首先可以做到一个判定性问题,也就是给定A集合的最大元素x1,和B集合的最大元素x2,我们可以判定我们能否得到这样的两个集合,因此顺理成章的就有了枚举x1然后二分猜x2的情况,这样要跑几分钟(如果动态维护要快一些,可以几秒跑完,但是少主很懒唉)
后来少主发现我们可以用单调指针完成,因为两边都有单调性了,这样就差不多4秒可以跑完,但是少主家的题库每组数据1S,很伤呀
后来少主又尝试了二分、三分,虽然这个并没有单调性,但是可以过很多组数据,(加起来可以过完的哦,但是少主是不愿意这么做的)
在将少主这个很牛X的方法之前,我们先前通过枚举+二分猜得到了一个很有力的工具,也就是一旦确定了x1,我们就可以确定x2的最小值,那么少主就想能不能也让x1跳得快一点呢,由此便有了以下算法
步骤(注意,所有的A,B都是建立在两个集合的可行解上面的,也就是所有出现过的值,还要手动加上0,也是作为二分答案的一个很好的剪枝,并且对应答案也一定是可行解)
1、确定边界A=1,并且通过二分确定边界最小B,记录一次答案
2、让B减1,确定出对应的A的最小值(肯定不比原来小,而且是对应B的最小可行解),然后再确定新的B(肯定比原来小,答案比确定新的B值之前肯定好些),记录下此时A、B对应的答案。
3、重复步骤2,直到B=1,但是为了不重复,可以加上剪枝,也就是A>=B的时候直接退出即可
这个算法并不是真正意义上的二分,只是利用了二分,实际上应该算是大步跳跃的一种,从步骤2我们大概可以看出一次更新AB之后,新的值肯定比与旧的AB之间的取值要好些,因此我们就可以大概确定我们得到了所有不错的解取一个最优值
时间复杂度非常玄学,经过实验快的可以只有几次更新,慢的是log的几倍,总的来说比较快,可以看做log吧。
希望有路过的大佬神犇能够之处正确性证明的错误或者正确性(我觉得有点牵强好像,但我对了几千组拍没问题,还过了刁钻的测试数据),或者说能够点明一下时间复杂度的奥秘,在下感激不尽(^~^)
代码
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=500;struct edge{ int to,next;}E[maxn*maxn];int np,first[maxn];void add(int u,int v){ E[++np]=(edge){v,first[u]}; first[u]=np;}int n;int d[maxn][maxn];bool vis[maxn];int a[maxn*maxn],cnt;int other[maxn];int stk[maxn],top;void Init(){ scanf("%d",&n); a[++cnt]=0; for(int i=1;i<=n;i++) { other[i]=i+n; other[i+n]=i; for(int j=i+1;j<=n;j++) { scanf("%d",&d[i][j]); a[++cnt]=d[i][j]; } } sort(a+1,a+cnt+1); cnt=unique(a+1,a+cnt+1)-a-1;}bool DFS(int i){ if(vis[i])return 1; if(vis[other[i]])return 0; vis[i]=1; stk[++top]=i; for(int p=first[i];p;p=E[p].next) if(!DFS(E[p].to)) return 0; return 1;}bool calc(int x,int mid)//i代表A,other代表B,x是a,other是b { np=0; memset(first,0,sizeof(first)); for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { if(d[i][j]>x) { add(i,other[j]); add(j,other[i]); } if(d[i][j]>mid) { add(other[i],j); add(other[j],i); } } } memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) { if(vis[i]||vis[other[i]])continue; top=0; if(!DFS(i)) { while(top) vis[stk[top--]]=0; if(!DFS(other[i])) return 0; } } return 1;}int work(int x){ int A=1,B=cnt,ret,mid; while(A<=B) { mid=(A+B)>>1; if(calc(a[x],a[mid])) ret=mid,B=mid-1; else A=mid+1; } return ret;}void solve(){ int A=1,B=work(A); int ans=a[A]+a[B]; while(A<B) { A=work(--B); B=work(A); ans=min(ans,a[A]+a[B]); } printf("%d\n",ans);}int main(){ Init(); solve(); return 0;}
注意:(为了刁钻的数据和实现代码的标准化)
1、由于答案可能就是最初的两端,那么初始答案应该直接使用两端作为结果
2、由于可能A和B相互推导是原来的那个数,A=B会进入死循环,因此更新答案范围的时候要求A < B才继续循环,而如果出现了A=B的情况,答案会统计到的
附录
问:为什么这里用2-SAT而不是二分图呢?
答:因为这里用了两个参数限制集合,也就是说集合A和集合B是不同的集合,然而二分图是不能区分集合的。
- [Wf2014]Metal Processing Plant(金属加工厂) 之这是小少主我用二分答案用得最6的一次(所有数据共1S)
- bzoj 4078 [Wf2014]Metal Processing Plant 2SAT 二分图
- 【BZOJ4078】[Wf2014]Metal Processing Plant【2-SAT】【二分】【二分图】【并查集】
- ACM-ICPC wf2014 金属加工厂 metal 单调性维护+2_SAT
- hdu 2962(二分答案+最短路)
- BZOJ 4077 Wf2014 Messenger 二分答案+计算几何
- 我的二分之路(1)
- Metal学习(1)
- 做人做得最失败的一次
- 【UVALive】6776 2014WorldFinal G Metal Processing Plant【2-sat——bitset优化kosaraju求scc】
- poj2289 Jamie's Contact Groups(二分答案+最大流)
- 数据可视化之Processing【1】
- sql习题1(我的答案)
- 最短路 + 二分答案
- 华哥倒酒(二分答案)
- HDU2333(二分答案)
- number (二分答案)
- crf 的视察 (二分答案)
- 计算机科学之父――图灵
- AOSP 下载单个分支
- Glass Carving CodeForces
- sts-bundle下载
- Employee Importance
- [Wf2014]Metal Processing Plant(金属加工厂) 之这是小少主我用二分答案用得最6的一次(所有数据共1S)
- var/run/nginx/nginx.pid" no such file or directory问题
- 51nod 1189 阶乘分数
- Openssl-MD5
- bzoj 1977 (浅谈如何hack掉hzwer学长)(严格次小生成树)(LCA+kruskal)
- 进程学习:4-僵尸进程
- 添加节点删除节点改变颜色等
- 笔试汇总
- 第五周—项目3——括号的匹配