2400: Spoj 839 Optimal Marks
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 681 Solved: 256
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
2
-1
0
1 2
2 3
Sample Output
2
HINT
数据约定
n<=500,m<=2000
样例解释
2结点的值定为0即可。
Source
题解:最小割
这道题直接表示边权不好办,因为异或运算的每一位都是互不影响的,所以我们可以单独考虑每一位。
sigma(u,v)∈E lv xor lu =sigma(u,v)∈E sigma i=0...mx (l[v,i] xor l[u,i])*2^i
mx 由u,v中较大的数决定。
我们将式子进一步转化
原式=sigma i=0...mx 2^i sigma(u,v)∈E l[v,i] xor l[u,i]
那么我们要求的答案其实就是对于每一位最小化sigma(u,v)∈E l[v,i] xor l[u,i]的取值,其中l[v,i]和l[u,i]只有{0,1}两种取值。
我们可以将这个问题转化成最小割求解。
设源点为S,汇点为T
对于一个点x,如果x的值是给出的
如果当前位的值为0,S->x 容量为INF
如果当前位的值为1,x->T 容量为INF
然后我们加入原图中的边,边权为1。INF的边不能砍断,所以如果发生冲突我们只能砍断原图的边,而砍断原图的边就相当于这条边连接的两个点取不同的值,那么会对这一位产生1的贡献。然后就用最大流求解最小割即可。
但是如果只是单单这样做,只能保证第一问最小,不能保证第二位最小。
所以我们可以对最小割的图进行巧妙的改造(这个我并不会,看的是网上的题解)
对于那些没有与S相连的点,我们连一条S->x容量为1的边,然后将原图中的边扩大100000倍。
然后再进行最小割,对于当前位来说,第一问的答案就是flow/100000,第二问的答案就是flow%100000
考虑这样做为什么是对的?这样做不会影响原最小割的答案,但是我们相当于在割的时候强制他尽可能割掉与T相连的边,也就是对于不确定的每一位我们尽可能让他选择0,这样就能保证点权最小。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 20003
#define inf 1000000000
#define LL long long
using namespace std;
int n,m,val[N],a[503][32],mx,mi[N],tot;
int point[N],v[N],remain[N],next[N];
int last[N],deep[N],cur[N],num[N],x[N],y[N],id[N],mark[N];
int size,head[N],u[N],nxt[N],opt[N];
LL ans,sum;
void add(int x,int y,int z,int k)
{
tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; id[tot]=k;
tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; id[tot]=k;
//cout<<x<<" "<<y<<" "<<z<<endl;
}
int addflow(int s,int t)
{
int now=t; int ans=inf;
while (now!=s) {
ans=min(ans,remain[last[now]]);
now=v[last[now]^1];
}
now=t;
while (now!=s) {
remain[last[now]]-=ans;
remain[last[now]^1]+=ans;
now=v[last[now]^1];
}
return ans;
}
void bfs(int s,int t)
{
queue<int> p;
for (int i=1;i<=t;i++) deep[i]=t;
deep[t]=0; p.push(t);
while (!p.empty()) {
int now=p.front(); p.pop();
for (int i=point[now];i!=-1;i=next[i])
if (deep[v[i]]==t&&remain[i^1])
deep[v[i]]=deep[now]+1,p.push(v[i]);
}
}
int isap(int s,int t)
{
int now=s; int ans=0;
bfs(s,t);
for (int i=1;i<=t;i++) num[deep[i]]++;
for (int i=1;i<=t;i++) cur[i]=point[i];
while (deep[s]<t) {
if (now==t) {
ans+=addflow(s,t);
now=s;
}
bool pd=false;
for (int i=cur[now];i!=-1;i=next[i])
if (deep[now]==deep[v[i]]+1&&remain[i]) {
last[v[i]]=i;
cur[now]=i;
now=v[i];
pd=true;
break;
}
if (!pd) {
int minn=t;
for (int i=point[now];i!=-1;i=next[i])
if (remain[i]) minn=min(minn,deep[v[i]]);
if (!--num[deep[now]]) break;
num[deep[now]=minn+1]++;
cur[now]=point[now];
if (now!=s) now=v[last[now]^1];
}
}
return ans;
}
void find(int x)
{
mark[x]=1;
for (int i=point[x];i!=-1;i=next[i])
if (remain[i]&&!mark[v[i]])
find(v[i]);
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&val[i]);
mx=0;
memset(a,-1,sizeof(a));
for (int i=1;i<=n;i++) {
if (val[i]<0) {
val[i]=-1;
continue;
}
int cnt=0; int x=val[i];
if (x==0) a[i][++cnt]=0;
while (x) {
cnt++;
a[i][cnt]=x&1;// cout<<a[i][cnt]<<" ";
x>>=1;
}
// cout<<endl;
mx=max(mx,cnt);
}
for (int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
mi[0]=1;
int s=1; int t=n+2;
for (int i=1;i<=30;i++) mi[i]=mi[i-1]*2;
ans=0; sum=0;
for (int i=1;i<=mx;i++) {
tot=-1;
memset(point,-1,sizeof(point));
memset(num,0,sizeof(num));
for (int j=1;j<=n;j++) {
if (val[j]!=-1) {
if (a[j][i]==0||a[j][i]==-1) add(s,j+1,inf,0);
else add(j+1,t,inf,0),add(s,j+1,1,0);
}
else add(s,j+1,1,0);
}
for (int j=1;j<=m;j++) {
add(x[j]+1,y[j]+1,100000,j);
add(y[j]+1,x[j]+1,100000,j);
}
int flow=isap(s,t);
// cout<<flow<<endl;
ans+=(LL)(flow/100000)*(LL)mi[i-1];
//memset(mark,0,sizeof(mark));
//find(s);
//for (int j=1;j<=n;j++)
//if (!mark[j+1]) sum+=mi[i-1];
sum+=(LL)(flow%100000)*(LL)mi[i-1];
}
printf("%I64d\n",ans);
printf("%I64d\n",sum);
}