定义无向图中的一条边的值为:这条边连接的两个点的值的异或值。
定义一个无向图的值为:这个无向图所有边的值的和。
给你一个有n个结点m条边的无向图。其中的一些点的值是给定的,而其余的点的值由你决定(但要求均为非负数),使得这个无向图的值最小。在无向图的值最小的前提下,使得无向图中所有点的值的和最小。
第一行,两个数n,m,表示图的点数和边数。
接下来n行,每行一个数,按编号给出每个点的值(若为负数则表示这个点的值由你决定,值的绝对值大小不超过10^9)。
接下来m行,每行二个数a,b,表示编号为a与b的两点间连一条边。(保证无重边与自环。)
第一行,一个数,表示无向图的值。
第二行,一个数,表示无向图中所有点的值的和。
3 2
2
-1
0
1 2
2 3
2
2
数据约定
n<=500,m<=2000
样例解释
2结点的值定为0即可。
二进制,按位处理。
首先%Oxer的题解
对于每一位单独处理。
每位只有两种可能:0或者1。所以可以把点集划分为两部分。考虑异或:相同为0,不同为1。也就是说只有两集合相邻部分会对答案有贡献,而我们的目标是贡献最小。
简单地说,就是把一个点集划分为两个集合,使它们相交部分贡献最小。这不就是最小割!
建模:源点向每个这位为0的点连INF边,这位为1的点向汇点连INF边。因为是无向图,所以原边 <u,v> ,建 <u,v,1><v,u,1> 。这样求一遍最小割,把原图划分为s集和e集,其中s集合中所有点为0,e集合中所有点为1。而这次对边权的贡献就是最小割的值。
然而在顾及边权和最小时,没有顾及点权和最小。换句话说就是边权和一样的时候不一定保证点权和最小。贪心的想:若想要点权和最小,则s集合中的点一定尽量多,也就是说当最小割有多个的时候,选靠近汇点的那个割。
有一种方法是从汇点dfs,找到的点全部标为1。仔细想想好像没什么错,因为最靠近汇点的割也是割,它会阻碍汇点dfs到更多的点。
还有一种方法比较神…就是把刚刚说的建模的容量乘10000,也就是原边 <u,v> ,建 <u,v,10000><v,u,10000> ,然后源点向每个点建 <s,u,1> ,这样做最小割的时候,肯定要先保证容量大的边少选,然后还要保证容量小的边少选。而同样的割若更靠近源点,容量小的边会选的更多,所以割会靠近汇点。
这样求一遍最大流ans,对边权和贡献是ans/10000,对点权和是ans%10000。
这种思想简单来说就是扩大第一关键字的值,在第一关键字相同使考虑第二关键字,实现两种权值选优的目的。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL INF = 10000000000000000ll;
const int SZ = 1000010;
const LL mod = 10000;
int head[SZ],nxt[SZ],tot = 1;
struct edge{
int t;
LL d;
}l[SZ];
void build(int f,int t,LL d)
{
l[++ tot].t = t;
l[tot].d = d;
nxt[tot] = head[f];
head[f] = tot;
}
void insert(int f,int t,LL d)
{
build(f,t,d); build(t,f,0);
}
int deep[SZ];
queue<int> q;
bool bfs(int s,int e)
{
memset(deep,0,sizeof(deep));
deep[s] = 1;
while(q.size()) q.pop();
q.push(s);
while(q.size())
{
int u = q.front(); q.pop();
for(int i = head[u];i;i = nxt[i])
{
int v = l[i].t;
if(!deep[v] && l[i].d)
{
deep[v] = deep[u] + 1;
q.push(v);
if(v == e) return true;
}
}
}
return false;
}
LL dfs(int u,LL flow,int e)
{
if(e == u || flow == 0) return flow;
LL rest = flow;
for(int i = head[u];i;i = nxt[i])
{
int v = l[i].t;
if(deep[v] == deep[u] + 1 && l[i].d)
{
LL f = dfs(v,min(rest,l[i].d),e);
if(f > 0)
{
l[i].d -= f;
l[i ^ 1].d += f;
rest -= f;
if(rest == 0) break;
}
else deep[v] = 0;
}
}
return flow - rest;
}
LL dinic(int s,int e)
{
LL ans = 0;
while(bfs(s,e)) ans += dfs(s,INF,e);
return ans;
}
int ff[SZ],tt[SZ],val[SZ],n,m;
void init()
{
tot = 1;
memset(head,0,sizeof(head));
}
void build_graph(int x,int s,int e)
{
init();
for(int i = 1;i <= n;i ++)
{
insert(s,i,1);
if(val[i] >= 0)
{
if(val[i] & (1 << x))
insert(i,e,INF);
else
insert(s,i,INF);
}
}
for(int i = 1;i <= m;i ++)
insert(ff[i],tt[i],mod),insert(tt[i],ff[i],mod);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
scanf("%d",&val[i]);
for(int i = 1;i <= m;i ++)
scanf("%d%d",&ff[i],&tt[i]);
int s = n + 1,e = n + 2;
LL ans1 = 0,ans2 = 0;
for(int i = 30;i >= 0;i --)
{
build_graph(i,s,e);
LL sum = dinic(s,e);
ans1 += sum / mod * (1ll << i);
ans2 += sum % mod * (1ll << i);
}
printf("%lld\n%lld",ans1,ans2);
return 0;
}