mst

https://www.zybuluo.com/ysner/note/1245941

题面

给一个\(n\)点完全图,点权均小于\(2^m\)。定义边权等于两端点点权的与和(即\(a_i\&b_i\))。求最大生成树边权和。

  • \(10pts\ n\leq8000\)
  • \(15pts\ m=1\)
  • \(25pts\ m\leq12\)
  • \(55pts\ m\leq15\)
  • \(90pts\ n\leq10^5,m\leq18\)
  • \(100pts\ n\leq5*10^6,m\leq20\)

解析

\(10pts\)算法

用\(Kruskal\)搞复杂度为\(O(n^2logn)\),会\(TLE\)。

可以用\(O(n^2)\)的\(Prim\)算法。

for(rg int i=1;i<=n;i++) Dis[i]=-Inf;
            Dis[1]=0;   ll Ans=0;
            for(rg int i=1;i<=n;i++)
            {   int Now=0,Maxd=-Inf;
                for(rg int j=1;j<=n;j++)
                    if(!Blue[j]&&Dis[j]>Maxd) Now=j,Maxd=Dis[j];
                Ans+=Maxd,Blue[Now]=1;
                for(rg int j=1;j<=n;j++)
                    if(!Blue[j])  Dis[j]=Max(Dis[j],A[Now]&A[j]);
            }

值得注意的是初始值极小,永远在取\(\max\)。

\(15pts\)算法

说明点权只能为\(0\)或\(1\)。

\(x\)个\(1\)点最多能连\(x-1\)条边。

\(ans=x-1\)。

\(25pts\)算法

注意到如果数值\(p\)重复出现\(x\)次,答案加上\((x-1)*p\)(相同数值相互连边)后,就可把该数值视为唯一。

于是\(n\)降到\(O(2^{12})=4096\),又可以\(Prim\)啦。

\(55pts\)算法

注意到若\(a\&b=b\),在\((a,b)\)间连边一定最优。于是可以找出每个点\(b\)的子集(复杂度\(O(15n)\)),若存在,优先建边并统计答案。

然后由于剩下点互不包含,点数会降到\(C_m^{m/2}\)(因为最多的情况是选所有有\(m/2\)个\(1\)的二进制数,而这些数因为\(1\)数量相同,肯定互不包含),似乎又可以暴力了。

\(90pts\)算法

这个算法很神仙。

论如何求集合中选一个数与当前值进行位运算的\(\max\)

(当然先\(orz\)CJrank1yyb)

如果是暴力的话,我们的方法有两种:

  • 对于当前数\(x\),暴力计算所有存在的数\(a_i\)中,\(x\bigoplus i\)的最大值,这样的复杂度是\(O(2^{16})\)。
  • 对于每个可能出现的数维护一个当前所有数的最大值。即每次插入时,暴力计算所有的答案的最大值,这样子询问的时候可以\(O(1)\)查询。

两种方法一种是插入\(O(1)\),询问\(O(n)\),另外一个是插入\(O(n)\),询问\(O(1)\)。

我们把两种东西结合一下,这样可以得到一个\(O(n\sqrt{n})\)的方法。

我们对于每个数从中间分开,拆成前\(8\)个二进制位和后\(8\)个二进制位。

这样子我们可以预处理一个数组\(t[i][j]\),

表示集合中一个前\(8\)位是\(i\)的数,后\(8\)位与\(j\)进行位运算的最大值。

因为位运算是可以按位贪心的,所以对于查询一个数\(x\),我们把它拆成\(x=a×2^8+b\)。

每次先暴力\(for\)所有可能的前\(8\)位,找到与\(a\)能够构成最大值的那些数,然后对于找到的所有数的前八位\(p\),直接查\(t[p][b]\)。

因为前八位更大的数一定更大,那么影响结果的就只剩下后八位了,

把两个部分拼接起来就好了。

这样子暴力\(for\)前八位的复杂度是\(O(2^8)\),查找后面部分最大值的复杂度是\(O(1)\)

所以这样子总的复杂度\(O(2^8)\)。

而对于集合中插入一个数,前\(8\)位唯一确定,每次只需要预处理后\(8\)位的结果。

时间复杂度还是\(O(2^8)\),总的复杂度还是\(O(\sqrt{n})\)。

最后记得反过来再跑一遍。

照搬该方法到本题,复杂度为\(O(n\sqrt{n})\)。

注意一下连边。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int mod=1e9+7,N=1e5+100;
int a[N],h[N],cnt,n,m,k,f[N],p=1,base,nxt[N],t[1<<11][1<<11],c[1<<11][1<<11],cp[N],tl[N],ma[N],fa[N];
ll ans;
il ll gi()
{
   re ll x=0,t=1;
   re char ch=getchar();
   while(ch!=‘-‘&&(ch<‘0‘||ch>‘9‘)) ch=getchar();
   if(ch==‘-‘) t=-1,ch=getchar();
   while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
   return x*t;
}
il int find(re int x){return fa[x]?fa[x]=find(fa[x]):x;}
il void link(re int u,re int v)
{
  re int fu=find(u),fv=find(v);
  if(fu^fv)
    {
      nxt[tl[fv]]=h[fu];
      tl[fv]=tl[fu];
      fa[fu]=fv;
      ++p;
    }
}
il void solve()
{
  memset(f,0,sizeof(f));memset(t,-1,sizeof(t));memset(ma,-1,sizeof(ma));
  fp(i,1,n)
    if(!fa[i])
    {
    for(re int j=h[i];j;j=nxt[j])
      {
    re int x=a[j]>>k,y=a[j]&base;//x代表前一半位,y代表后一半位
    fp(z,0,(1<<m-k)-1)//枚举前一半位并统计答案
      if(f[z])
        {
          re int ss=a[j]&t[z][y]|(z<<k&a[j]);//组合
          if(ss>ma[i]) ma[i]=ss,cp[i]=c[z][y];
        }
      }
  for(re int j=h[i];j;j=nxt[j])
    {
      re int x=a[j]>>k,y=a[j]&base;f[x]=1;//标记其存在
      fp(z,0,(1<<k)-1)//枚举后一半位并处理
    if((y&z)>t[x][z]) t[x][z]=y&z,c[x][z]=i;
    }
    }
  memset(f,0,sizeof(f));memset(t,0,sizeof(t));
  fq(i,n,1)
    if(!fa[i])
      {
    for(re int j=h[i];j;j=nxt[j])
      {
    re int x=a[j]>>k,y=a[j]&base;
    fp(z,0,(1<<m-k)-1)
      if(f[z])
        {
          re int ss=a[j]&t[z][y]|(z<<k&a[j]);
          if(ss>ma[i]) ma[i]=ss,cp[i]=c[z][y];
        }
      }
  for(re int j=h[i];j;j=nxt[j])
    {
      re int x=a[j]>>k,y=a[j]&base;f[x]=1;
      fp(z,0,(1<<k)-1)
    if((y&z)>t[x][z]) t[x][z]=y&z,c[x][z]=i;
    }
      }
  fp(i,1,n)
    if(!fa[i])
      {
    re int u=find(i),v=find(cp[i]);
    if(u^v) ans+=ma[i],link(i,cp[i]);
      }
}
int main()
{
  freopen("mst.in","r",stdin);
  freopen("mst.out","w",stdout);
  n=gi();m=gi();k=m/2;base=(1<<k)-1;
  fp(i,1,n)
    {
      a[i]=gi();
      h[i]=tl[i]=i;
    }
  while(p<n) solve();
  printf("%lld\n",ans);
  fclose(stdin);
  fclose(stdout);
  return 0;
}

\(100pts\)算法

倒序枚举用于连接的边权\(p\),用\(a_p\)表示\(p\)的最大超集(只多一个\(1\))所在联通块的代表元。枚举\(p\)的另一超集(只多一个\(1\)),若两者不在同一联通块,合并之。从大到小保证最优性。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int mod=1e9+7,N=1e5+100;
int a[1<<21],h[N],cnt,n,m,f[1<<21];
ll ans;
il ll gi()
{
   re ll x=0,t=1;
   re char ch=getchar();
   while(ch!=‘-‘&&(ch<‘0‘||ch>‘9‘)) ch=getchar();
   if(ch==‘-‘) t=-1,ch=getchar();
   while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
   return x*t;
}
il int find(re int x){return f[x]?f[x]=find(f[x]):x;}
int main()
{
  freopen("mst.in","r",stdin);
  freopen("mst.out","w",stdout);
  n=gi();m=gi();
  fp(i,1,n)
    {
      re int x=gi();
      if(a[x]) ans+=x;
      a[x]=x;
    }
  fq(t,(1<<m)-1,0)
    {
      re int &u=a[t];
      for(re int i=0;!u&&i<m;i++) u=a[t|(1<<i)];
      if(!u) continue;
      fp(i,0,m-1)
    {
      re int v=a[t|(1<<i)],fu=find(u),fv=find(v);
      if(v&&fu!=fv) ans+=t,f[fv]=fu;
    }
    }
  printf("%lld\n",ans);
  fclose(stdin);
  fclose(stdout);
  return 0;
}

原文地址:https://www.cnblogs.com/yanshannan/p/9457759.html

时间: 2024-11-05 15:50:31

mst的相关文章

ACdream 1135 MST

MST Time Limit: 2000/1000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) Problem Description Given a connected, undirected graph, a spanning tree of that graph is a subgraph that is a tree and connects all the vertices together.  A single

poj1789 MST 读题生涯永不停歇

题目: 链接在此 1.图论刷刷乐#1的第一题,无奈看了好长时间题目还是看不懂= =,明知是最水的题目 2.搜懂题目后,比较裸的MST,但还是决定写个题解,虽然没什么可说的,只是来警戒自己,还是要努力读题,YY大法,这也是水平的一个体现! #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> #include &

【BZOJ 2654】 MST

2654: tree Description 给你一个无向带权连通图,每条边是黑色或白色.让你求一棵最小权的恰好有need条白色边的生成树. 题目保证有解. Input 第一行V,E,need分别表示点数,边数和需要的白色边数. 接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色). Output 一行表示所求生成树的边权和. V<=50000,E<=100000,所有数据边权为[1,100]中的正整数. Sample Input 2 2 1 0 1

POJ 1679 The Unique MST(次短生成树)

Language: Default The Unique MST Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 29098   Accepted: 10404 Description Given a connected undirected graph, tell if its minimum spanning tree is unique. Definition 1 (Spanning Tree): Consider

HDU 4126 Genghis Khan the Conqueror (树形DP+MST)

题意:给一图,n个点,m条边,每条边有个花费,给出q条可疑的边,每条边有新的花费,每条可疑的边出现的概率相同,求不能经过原来可疑边 (可以经过可疑边新的花费构建的边),注意每次只出现一条可疑的边,n个点相互连通的最小花费的期望. 析:要想连通先让他们连通起来,先构造出一个MST,然后再暴力,如果这个边不在这里面,那么花费不变,如果在里面,那我们需要知道是用原来的边最少, 还是再找一条边使他们连通起来,这里就要先预处理了,dp[i]j[i] 表示 左边的那个一半 i 和 右边那一半 j 的最长距离

The Unique MST

#include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <queue> const int INF=0x3f3f3f3f; using namespace std; const int M=110; int n,m; vector<int> v[M]; int G[M][M]; struct Node { int u,v;

HDU 5627Clarke and MST

Clarke and MST Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 315    Accepted Submission(s): 176 Problem Description Clarke is a patient with multiple personality disorder. One day he turned in

[2016-01-27][POJ][1679][The Unique MST]

C - The Unique MST Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit Status Practice POJ 1679 Description Given a connected undirected graph, tell if its minimum spanning tree is unique. Definition 1 (Spanning Tree):

POJ1679 The Unique MST(次小生成树)

可以依次枚举MST上的各条边并删去再求最小生成树,如果结果和第一次求的一样,那就是最小生成树不唯一. 用prim算法,时间复杂度O(n^3). 1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 #define MAXN 111 5 #define INF (1<<30) 6 struct Edge{ 7 int u,v; 8 }edge[MAXN]; 9 int NE; 10 11 int n

poj1679The Unique MST判断最小生成树是否唯一以及求次小生成树边权和的讲解

Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 22346   Accepted: 7924 Description Given a connected undirected graph, tell if its minimum spanning tree is unique. Definition 1 (Spanning Tree): Consider a connected, undirected graph G =