Loading

1079. 【GDKOI2007】纳克萨玛斯

Description

“英雄们,天灾亡灵军团已经入侵到暴风城的周围村庄了,为了艾泽拉世界的未来让我们携手对抗敌人吧!”先知 eternized 召集了联盟和部落的领袖们,准备组建一只讨伐军征战“纳克萨玛斯”——天灾亡灵的大本营。
三天过后,联盟首领 bug 和部落首领 spacesheep 分别带来了各自阵营里面最精英的部队过来。这批勇士一共有 N 人,属于各种族和各种职业。他们不但有能沟通大自然的牛头人德鲁依,也有圣光保佑的圣骑士矮人,甚至还有能连通阴阳界的精灵术士。
“好吧,现在我们来挑选勇士吧。”先知首先挑选中一些勇士,然后将他们分为一个个单独的小队,其中每个小队包括 M 种角色,比如说队长(leader),攻击(Attack)、治疗(Cure)、防御(Tank)等,而且队中每种角色有且仅有一个。一个人只能充当一个角色。比如德鲁依,受到自然的恩惠,使他能成为一个合格的医疗师和防御者,而暗影牧师则能提供足够的攻击力和防御力。另外,先知为了平衡各方阵营利益,联盟和部落各被选上的人数相差不能超过 D 人。
先知当然希望能组织起来的小队越多越好。聪明的你,你能给出一个最优方案么?

Solution

先不管 \(D\) 这个限制,那么我们可以建源点和汇点,分别连向人和职业,同时枚举答案 \(ans\)(当然也可以二分),其中职业向汇点的边的流量为 \(ans\),源点向人的流量为 1,人向职业的流量为 \(\inf\),每次重新建图跑最大流,直到最大流 \(flow<ans\times m\),那么 \(ans-1\) 就是答案。

现在考虑加上限制 \(D\)。设联盟人数为 \(x\),部落人数为 \(y\),假定 \(x>y\)。那么有 \(x+y=ans\times m\)\(x-y\le D\)。变一下可以得到 \(x\le \lfloor\frac{D+ans\times m}{2}\rfloor\)

因此在建图时,改一下源点到人的连接方式,新增两个节点,一个连联盟,一个连部落,到人的流量为 1。源点流向这两个点的流量都为 \(\lfloor\frac{D+ans\times m}{2}\rfloor\)。这样就可以满足限制。

但这样的话边的数量可能会达到 \(10^6\),网络流肯定会超时,考虑优化建图方法。

发现即使所有状态都存在的话,每个人的类型最多只有 \(2^m\) 种,远远小于 \(n\)。并且每个状态相同的人之间是没有区别的,因此我们可以记录每种状态的数量,将流量从1 改为数量,并且左边不再是人而是状态,可以减少边数,从而降低复杂度。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3000
#define inf 2147483647
using namespace std;
struct node
{
    int to,next,flow;
}a[N*20];
int n,m,d,ans,tot,S,T,SS,TT,mx,answer,t[2000][2],head[N],cur[N],deep[N];
char ch[20];
void add(int x,int y,int z)
{
    a[++tot].to=y;a[tot].flow=z;a[tot].next=head[x];head[x]=tot;
    a[++tot].to=x;a[tot].flow=0;a[tot].next=head[y];head[y]=tot;
}
bool bfs()
{
    memset(deep,0,sizeof(deep));
    queue<int> q;
    deep[S]=1;q.push(S);
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int i=head[x];i!=-1;i=a[i].next)
        {
            int y=a[i].to;
            if (!deep[y]&&a[i].flow)
            {
                deep[y]=deep[x]+1;
                q.push(y);
                if (y==T) return true;
            }
        }
    }
    return false;
}
int dfs(int x,int flow)
{
    if (x==T) return flow;
    int res=0;
    for (int &i=cur[x];i!=-1;i=a[i].next)
    {
        int y=a[i].to;
        if (a[i].flow&&deep[y]==deep[x]+1)
        {
            int fl=dfs(y,min(flow,a[i].flow));
            if (!fl) continue;
            a[i].flow-=fl;a[i^1].flow+=fl;
            res+=fl;flow-=fl;
            if (!flow) break;
        }
    }
    return res;
}
int main()
{
    scanf("%d%d%d",&n,&m,&d);
    for (int i=1;i<=n;++i)
    {
        scanf("%s",ch+1);
        int typ=ch[m+1]-'0',sum=0;
        for (int j=1;j<=m;++j)
            sum+=(ch[j]-'0')*(1<<(j-1));
        ++t[sum][typ];
    }
    mx=(1<<m);
    S=2*mx+m;T=S+1;SS=T+1;TT=SS+1;
    for (ans=1;ans<=n;++ans)
    {
        memset(head,-1,sizeof(head));
        tot=-1;
        add(S,SS,(d+ans*m)/2);add(S,TT,(d+ans*m)/2);
        for (int i=0;i<mx;++i)
            add(SS,i,t[i][0]),add(TT,i+mx,t[i][1]);
        for (int i=0;i<=mx;++i)
            for (int j=0;j<m;++j)
                if (((1<<j)&i)) add(i,j+2*mx,inf),add(i+mx,j+2*mx,inf);
        for (int i=1;i<=m;++i)
            add(i+2*mx-1,T,ans);
        int res=0;
        while (bfs())
        {
            for (int i=1;i<=TT;++i)
                cur[i]=head[i];
            res+=dfs(S,inf);
        }
        if (res<ans*m&&d!=0)
        {
            printf("%d\n",ans-1);
            return 0;
        }
        else if (res>=ans*m) answer=ans;
    }
    printf("%d\n",answer);
    return 0;
}
posted @ 2022-01-20 21:14  Thunder_S  阅读(25)  评论(0编辑  收藏  举报