2280. 最优标号

题目链接

2280. 最优标号

给定一个无向图 \(G=(V,E)\),每个顶点都有一个标号,它是一个 \([0,2^{31}-1]\) 内的整数。

不同的顶点可能会有相同的标号。

对每条边 \((u,v)\),我们定义其费用 \(cost(u,v)\)\(u\) 的标号与 \(v\) 的标号的异或值。

现在我们知道一些顶点的标号。

你需要确定余下顶点的标号使得所有边的费用和尽可能小。

输入格式

第一行有两个整数 \(N,M\)\(N\) 是图的点数,\(M\) 是图的边数。

接下来有 \(M\) 行,每行有两个整数 \(u,v\),代表一条连接 \(u,v\) 的边。

接下来有一个整数 \(K\),代表已知标号的顶点个数。

接下来的 \(K\) 行每行有两个整数 \(u,p\),代表点 \(u\)标号\(p\)

假定这些 \(u\) 不会重复。

所有点编号\(1\)\(N\)

输出格式

输出一行一个整数,即最小的费用和。

数据范围

\(1 \le N \le 500\),
\(0 \le M \le 3000\),
\(1 \le K \le N\)

输入样例:

3 2
1 2
2 3
2
1 5
3 100

输出样例:

97

解题思路

最小割

由于是异或和,可以每一位单独考虑,对于第 \(k\) 位而言,可以分为 \(0\)\(1\) 两个集合,即建立源点 \(s\),第 \(k\) 位为 \(1\) 的点放在 \(s\) 这边形成最小割的 \(S\) 部分,\(s\) 向这部分点连边,容量足够大,建立汇点 \(t\),第 \(k\) 位为 \(0\) 的点放在 \(t\) 这边形成最小割的 \(T\) 部分,这部分点向 \(t\) 连边,容量也足够大,已知标号的点显然可以建立和 \(s\)\(t\) 的关系,对于未知标号的点,考虑流网络直接在原图上建边,即对于原图上的任意一条边 \((x,y)\),直接在流网络上建立从 \(x\)\(y\) 以及 \(y\)\(x\) 的边,容量为 \(1\),最后求解最小割 \(x\) 即为第 \(k\) 的贡献,即对于第 \(k\) 位来说至少有 \(x\) 条边该位为 \(1\),即该位答案至少为 \(m\times 2^k\)\(\color{red}{为什么?}\)显然求的是最小割,即割上不应该出现容量足够大的边,即一开始所有已知标号的点的第 \(k\) 位就已经分好了位置,即对于已知第 \(k\) 位为 \(1\) 的点分在最小割的 \(S\) 部分,对于第 \(k\) 位为 \(0\) 的点分在最小割的 \(T\) 部分,要使这样的异或为 \(1\) 的边最少,因为对于 \(S\)\(T\) 其内部形成的边的异或都为 \(0\),所以只考虑两部分中间的边,要求这样的边越少越好,即 \(S\)\(T\) 两部分之间的边的容量越少越好,即最小割(开始流网络在原图上建边时每条边的容量设为 \(1\),最小割值为多少就说明有多少条边)

  • 时间复杂度:\(O(30\times n^2m)\)

代码

// Problem: 最优标号
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2282/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=505,M=(3005+2*N)*2,inf=1e9;
int n,m,k,S,T;
PII edge[3005];
int h[N],ne[M],f[M],e[M],idx;
int p[N];
int q[N],hh,tt,cur[N],d[N];
void add(int a,int b,int c1,int c2)
{
	e[idx]=b,f[idx]=c1,ne[idx]=h[a],h[a]=idx++;
	e[idx]=a,f[idx]=c2,ne[idx]=h[b],h[b]=idx++;
}
void build(int k)
{
	memset(h,-1,sizeof h);
	idx=0;
	for(int i=1;i<=m;i++)
	{
		int x=edge[i].fi,y=edge[i].se;
		add(x,y,1,1);
	}
	for(int i=1;i<=n;i++)
		if(p[i]!=-1)
		{
			if(p[i]>>k&1)add(S,i,inf,0);
			else
				add(i,T,inf,0);
		}
}
bool bfs()
{
	memset(d,-1,sizeof d);
	d[0]=hh=tt=0;
	cur[S]=h[S];
	q[0]=S;
	while(hh<=tt)
	{
		int x=q[hh++];
		for(int i=h[x];~i;i=ne[i])
		{
			int y=e[i];
			if(d[y]==-1&&f[i])
			{
				d[y]=d[x]+1;
				cur[y]=h[y];
				if(y==T)return true;
				q[++tt]=y;
			}
		}
	}
	return false;
}
int dfs(int x,int limit)
{
	if(x==T)return limit;
	int flow=0;
	for(int i=cur[x];~i&&flow<limit;i=ne[i])
	{
		cur[x]=i;
		int y=e[i];
		if(d[y]==d[x]+1&&f[i])
		{
			int t=dfs(y,min(f[i],limit-flow));
			if(!t)d[y]=-1;
			f[i]-=t,f[i^1]+=t,flow+=t;
		}
	}
	return flow;
}
LL dinic(int k)
{
	int res=0,flow;
	build(k);
	while(bfs())while(flow=dfs(S,inf))res+=flow;
	return res;
}
int main()
{
	memset(p,-1,sizeof p);
    scanf("%d%d",&n,&m);
    S=0,T=n+1;
    for(int i=1;i<=m;i++)
    	scanf("%d%d",&edge[i].fi,&edge[i].se);
	scanf("%d",&k);
	for(int i=1;i<=k;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		p[x]=y;
	}
	LL res=0;
	for(int i=0;i<=30;i++)res+=dinic(i)<<i;
	printf("%lld",res);
    return 0;
}
posted @ 2022-11-28 19:54  zyy2001  阅读(43)  评论(0编辑  收藏  举报