Balance of the Force (枚举+线段树+二分图)
题意:有两个阵营,分别为光明和黑暗,现在有n个骑士,每个骑士都能选择加入黑暗或者光明的阵营,加入后的能力值分别为D和L,已知有m对骑士不愿意在同一个阵营,请问如何分配,能使得能力最高的骑士和能力最低的骑士之间的能力差值最小?
题解:(参考大佬博客)
对于差值尽量小的问题,可以采用枚举最大值,然后使得最小值尽量大。此题关键点便是枚举最大值,找到最大的最小值,更新答案。
看到这种不能选同一种的必然想到二分图,我们就把不愿意在同一阵营的分成二分图,如果不能分成二分图,那肯定就是\(impossible\)了。一个二分图是一个连通块,有多个,因为不是全部都有“联系”(不愿意在同一个阵营)
对于每一个连通块,可以知道有两种方案,(比如说二分图的左边部分在光明,右边在黑暗,也可以左边在黑暗,右边在光明),记录下这两种方案。每个方案都要记录这个方案的最大值\(mx\)和最小值\(mn\)
将这些方案按照最大值从小到大排序。从左到右依次枚举最大值 \(curmx\)。
假设当前位置最大值为\(curmx\),位置为\(i\)。
如果\(\left [ 1,i-1\right ]\)中的连通块个数等于所建立的二分图连通块的个数,那么就可以找\(mn\)的最小值\(curmn\)。更新下答案 \(ans = min\left ( ans,curmx-curmn\right )\)。
但是有个地方要注意,可能得出的\(curmn\)是当前方案i的孪生方案中的\(mn\)(每个块会有两种方案),因为每个连通块只能选一个方案,所以必须把前面这个孪生方案的\(mn\)贡献给去掉。这样求出来的\(curmn\)就是正确的。
枚举完这个\(i\)后,应该更新当前位置的\(mn\),同时如果前面有孪生方案\(j\)的话,那么两个位置都需要更新一下,更新的值便是\(max(a[j].mn, a[i].mn)\)。因为要让最小值尽量大。
对于最小值的维护就要用到线段树了,最基本的单点修改,区间查询问题
上述的步骤可以用数据结构在\(logn\)时间内完成。总复杂度为\(O(logn)\)
AC_Code:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 #define endl '\n' 5 const int inf=0x3f3f3f3f; 6 const int maxn=2e5+10; 7 8 vector<int>G[maxn]; 9 int color[maxn],n,m,val[maxn][2],sum[maxn]; 10 int tree[maxn<<3],pos[maxn]; 11 bool vis[maxn], flag;; 12 13 struct node{ 14 int mx,mn; 15 int id; 16 bool operator < (const node &rhs) const{ 17 return mx<rhs.mx; 18 } 19 }a[maxn<<1]; 20 21 void init(){ 22 for(int i=0;i<=n;i++){ 23 G[i].clear(); 24 pos[i]=sum[i]=color[i]=0; 25 vis[i]=false; 26 } 27 flag=true; 28 } 29 30 void dfs(int u, node &c1, node &c2){ 31 if( !flag ) return ; 32 c1.mx = max(c1.mx, val[u][0]); 33 c1.mn = min(c1.mn, val[u][0]); 34 c2.mx = max(c2.mx, val[u][1]); 35 c2.mn = min(c2.mn, val[u][1]); 36 for( auto v : G[u] ){ 37 if( color[v]==0 ){ 38 color[v]=3-color[u]; 39 dfs(v,c2,c1); //注意是c2,c1,不是c1,c2,因为相连的点要是一个取L,一个取D的 40 }else if( color[v]==color[u] ){ 41 flag=false; 42 return ; 43 } 44 } 45 } 46 47 void build(int rt,int l,int r){ 48 tree[rt]=inf; 49 if( l==r ){ 50 return ; 51 } 52 int mid=(l+r)>>1; 53 build(rt<<1,l,mid); 54 build(rt<<1|1,mid+1,r); 55 tree[rt]=min(tree[rt<<1],tree[rt<<1|1]); 56 } 57 58 void update(int rt,int pos,int v,int l,int r){ 59 if( l==r ){ 60 tree[rt]=v; 61 return ; 62 } 63 int mid=(l+r)>>1; 64 if( mid>=pos ) update(rt<<1,pos,v,l,mid); 65 else update(rt<<1|1,pos,v,mid+1,r); 66 tree[rt]=min(tree[rt<<1],tree[rt<<1|1]); 67 } 68 69 int query(int L,int R,int l,int r,int rt){ 70 if( l<=L && R<=r ) return tree[rt]; 71 72 int mid=(l+r)>>1; 73 int res=inf; 74 if( L<=mid ) res = min(res,query(L,R,l,mid,rt<<1)); 75 if( R>mid ) res = min(res,query(L,R,mid+1,r,rt<<1)); 76 return res; 77 } 78 79 int main() 80 { 81 int T,cas=0; scanf("%d",&T); 82 while( T-- ){ 83 init(); 84 scanf("%d%d",&n,&m); 85 for(int i=0,u,v;i<m;i++){ 86 scanf("%d%d",&u,&v); 87 G[u].push_back(v); 88 G[v].push_back(u); 89 } 90 for(int i=1;i<=n;i++) scanf("%d%d",&val[i][0],&val[i][1]); 91 int sz=1,cnt=0; 92 for(int i=1;i<=n;i++){//sz[x]与sz[x+1]组成了分别为一个连通块里的两种情况 93 if( !color[i] ){ 94 cnt++; 95 a[sz].mx=-inf, a[sz].mn=inf, a[sz].id=i; 96 a[sz+1]=a[sz]; 97 color[i]=1; 98 dfs(i,a[sz],a[sz+1]); 99 sz+=2; 100 } 101 } 102 printf("Case %d: ",++cas); 103 if( !flag ){ //不能全部都建成二分图 104 printf("IMPOSSIBLE\n"); 105 continue; 106 } 107 sort(a+1,a+sz); //按每一个连通块(每种两个情况)的最大值从小到大排序 108 109 for(int i=1;i<sz;i++){ 110 sum[i]=sum[i-1]; //sum[i]记录当前连通块前面有几个连通块 111 if( !vis[a[i].id] ){ 112 sum[i]++; 113 vis[a[i].id]=true; 114 } 115 } 116 117 build(1,1,sz-1);//建立一个维护最小值的线段树 118 int ans=inf; 119 for(int i=1;i<sz;i++){ 120 int curmx = a[i].mx, curmn=a[i].mn; 121 if( sum[i]==cnt ){ //如果该种方案前面的连通块个数加上当前这种的可以组成全部的连通块,即达到了总的个数 122 if( pos[a[i].id] ){//当前这个连通块的另一种情况(每个连通块有两个状态)在前面已经出现过了 123 update(1,pos[a[i].id],inf,1,sz-1);//而一个连通块只能选择其中一种方案,所以要把前一个方案的最小值给去掉,避免同一连通块另一种方案的影响 124 } 125 curmn = min(curmn, query(1,i-1,1,sz-1,1)); 126 ans = min(ans,curmx-curmn); 127 } 128 if( pos[a[i].id] ){ //如果一个连通块的两种情况都出现了 129 int ml = max(a[i].mn, a[pos[a[i].id]].mn );//选择其中那个最小值较大的放进线段树对后面产生影响 130 update(1,pos[a[i].id],ml,1,sz-1); 131 update(1,i,ml,1,sz-1); 132 }else{ 133 update(1,i,a[i].mn,1,sz-1);//如果第一次出现,直接放入线段树 134 pos[a[i].id]=i; 135 } 136 } 137 printf("%d\n",ans); 138 } 139 return 0; 140 }