保镖(hall定理&&集合动规&&优化)

来源:互联网 发布:秦风拍牌软件 编辑:程序博客网 时间:2024/05/01 09:20

【问题描述】

  蒟蒻YxuanwKeith想成为Philisweng的保镖,但是作为预备队员的保镖智商肯定也不能低,至少要回答出下面这个问题:现在有一副若干条边的二分图,左边有N个点ai,右边有M个点bi,每个点都有一个权值wi。一个合法的子图满足以下两个限制:

  1.选出的点权和大于等于限制t。
  2.并且可以从图中选出若干条边,使得二分图中每个点最多被一条边覆盖,而选出的点要恰好被一条边覆盖。

  求总方案数。由于YxuanwKeith很弱,所以他找到你来回答这个问题。

【输入格式】

  第一行包括两个数N,M,分别表示左边点的个数和右边点的个数。
  第2行到第N+1行,第i行一个长度为M的字符串Si,第j个字符如果是0表示左边第i-1个点和右边第j个点没有连边,如果是1表示有连边。
  第N+2行,N个非负整数,第i个非负整数表示左边第i个点的权值Wi
  第N+3行,M个非负整数,第i个非负整数表示右边第i个点的权值vi
  第N+4行,一个正整数t,表示题目中的限制。

【输出格式】

  输出共一行,一个数,表示答案。

【输入样例】

3 3
010
111
010
1 2 3
8 5 13
21

【输出样例】

3

【样例解释】

样例中的二分图如图所示:
  
子集{a1,a2,b2,b3}可以通过选择边集{(a1,b2),(a2,b3)}满足条件二,并且权值和是21。
子集{a3,b2,b3}和{a2,a3,b2,b3}都可以通过选择边集{(a2,b2),(a3,b2)}满足两个条件。
其余方案都不合法,所以答案是3。

【数据范围】

对于30%的数据:N,M<=8
对于另外30%的数据:左边的点和右边的点两两间都有连边。
对于100%的数据:N,M<=20,t<=4*10^8,wi,vi<=10^8

【来源】

https://jzoj.net

一道很神奇的题,开始用的网络流,结果全错。
正解是集合动规,主要是要用hall定理(请自行百度)
根据hall定理我们可以用动规的思想来查看一个集合是否成立,然后把权值和记录下来,这样我们就可以得到2个数组。(不成立的权值应该是负无穷)
然后一个排序,在另一个上进行二分查找就可以了。
一些必要的优化请自行查看代码

完整代码如下:

#include<cstdlib>#include<cstdio>#include<cstring>#include<iostream>#include<vector>#include<algorithm>using namespace std;typedef long long ll;const int maxn=45;const ll inf=20000000000000ll;vector<int>g[maxn];ll d[1<<21]={0},q[1<<21]={0};int m,n,a[maxn];bool vis[21];ll b[maxn];ll t;char s[maxn];ll work(int x){    int num1=0,num2=0;    memset(vis,0,sizeof(vis));    for(int i=1;i<=n;i++) if((a[i]&x)==a[i])    {        num1++;        for(int k=0;k<g[i].size();k++) if(!vis[g[i][k]])        vis[g[i][k]]=1,num2++;        if(num2>=n-i+num1) return 1;    }    return num1<=num2;}ll work2(int x){    int num1=0,num2=0;    memset(vis,0,sizeof(vis));    for(int i=1;i<=m;i++) if((a[i]&x)==a[i])    {        num1++;        for(int k=0;k<g[i+n].size();k++) if(!vis[g[i+n][k]])        vis[g[i+n][k]]=1,num2++;        if(num2>=m-i+num1) return 1;    }    return num1<=num2;}int main(){    //freopen("in.txt","r",stdin);    //freopen("out.txt","w",stdout);    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++)    {        scanf("%s",s);        for(int k=0;k<m;k++) if(s[k]=='1')        {            g[i].push_back(k+1);            g[k+1+n].push_back(i);        }    }    for(int i=1;i<=n+m;i++) cin>>b[i];    cin>>t;    a[1]=1;    for(int i=2;i<=max(n,m);i++)a[i]=a[i-1]*2;    int k1=1<<n,k2=1<<m;    for(int i=1;i<k1;i++)    {        if(d[i]!=-inf&&!work(i)) //根据hall定理进行判断        for(int j=i;j<k1;j++) if((i&j)==i)        d[j]=-inf;    }    for(int i=1;i<k2;i++)    {        if(q[i]!=-inf&&!work2(i))        for(int j=i;j<k2;j++) if((i&j)==i)        q[j]=-inf;    }    for(int i=1;i<k1;i++) if(d[i]!=-inf)    for(int j=1;j<=n;j++) if((a[j]&i)==a[j])//算权值和的时候的优化    {        d[i]=b[j]+d[i-a[j]];        break;    }    for(int i=1;i<k2;i++) if(q[i]!=-inf)    for(int j=1;j<=m;j++) if((a[j]&i)==a[j])    {        q[i]=b[j+n]+q[i-a[j]];        break;    }    sort(d,d+k1);    ll ans=0;    for(int i=0;i<k2;i++) if(q[i]!=-inf)//二分找答案    {        ans+=k1-(lower_bound(d,d+k1,t-q[i])-d);    }    cout<<ans;    return 0;}
1 0
原创粉丝点击