[TJOI2011]P1418 题解
Drind
·
·
题解
题目给定一个 01 矩阵每行和每列的 1 的个数,求字典序最小的满足条件的矩阵.
如果只需要给出一个构造那么这题非常简单,只需要建立一个二分图,左边有 $n
显然这样的构造不满足字典序最小,于是我们想,对于每个边,肯定是更靠前的边更重要,那是否可以给每个边一个费用跑费用流呢?事实上也是不行的,如果要让边之间互相不影响的话,费用最大的边必须达到 $2^{nm}$,显然是会爆 long long 的,这种方法不可行.
那么,既然每条经过增广的边都代表这个点是 $1$ 的话,我们是否可以把这条边删掉再跑一遍最大流,判断这条边是否必须呢?
如果用这种思路做的话,首先我们先贪心地从大到小枚举每个点,先跑一遍最大流,然后看看这个点对应的边是否被增广,如果没有被增广说明这个点不必要,输出 $0$;如果这个点对应的边被增广,那么就删掉这条边,再跑一遍最大流,如果这次最大流还能找到一条增广路替代这条边的话,就说明这个点不必要,输出 $0$;如果第二次最大流找不到增广路了,说明这个点必要,输出 $1$.
不用考虑如果前面的点取 $0$ 后是否会对后面的点产生影响,因为只要求字典序最小,前面的尽可能小,后面的再大也无所谓.
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
struct node
{
int to,nxt;
int flow;
}edge[500001];//链式前向星
int cnt=1,s,t,n,m;
int head[1021];
int cur[1021];
int dep[1021];
int r[10001];
int c[10001];
int id[201][201];//id数组代表点对应的边,其中第一维的n+1代表连向汇点的边,第二位的m+1代表连向源点的边
void add(int u,int v,int w)
{
edge[++cnt]={v,head[u],w};
head[u]=cnt;
edge[++cnt]={u,head[v],0};
head[v]=cnt;
}//一次加正向和反向边
//以下为dinic板子
bool bfs()
{
memset(dep,0,sizeof(dep));
queue<int> q;
dep[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dep[v]&&edge[i].flow>0)
{
dep[v]=dep[u]+1;
q.push(v);
if(v==t)
return true;
}
}
}
return false;
}
int dfs(int u,int f)
{
if(u==t)
return f;
int r=f;
for(int i=head[u];~i&&r;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].flow>0)
{
int temp=dfs(v,min(r,edge[i].flow));
if(!temp)
dep[v]=0;
edge[i].flow-=temp;
edge[i^1].flow+=temp;
r-=temp;
}
}
return f-r;
}
int dinic()
{
int mxflo=0;
while(bfs())
{
mxflo+=dfs(s,1e9);
}
return mxflo;
}//以上为dinic板子
int check(int x,int y)//check函数代表第(x,y)个点是否必须
{
dinic();
if(!r[x]||!c[y]) return 0;//如果没有0可以放了,则输出1
r[x]--,c[y]--;//假设放置0,行列0的数量都减一
if(edge[id[x][y]].flow)//如果这个点未被增广,则不必要,输出0
{
edge[id[x][y]].flow=0;//删除这个点对应的边,以防今后再次使用
return 1;
}
else
{
edge[id[x][y]^1].flow=0;//删除这条边,再次跑最大流,看看这条边是否必要
edge[id[x][y]].flow=0;
edge[id[x][m+1]^1].flow--;
edge[id[x][m+1]].flow++;
edge[id[n+1][y]^1].flow--;
edge[id[n+1][y]].flow++;
if(dinic()==1)//如果找到新的增广路替代则这个点不必要
return 1;
r[x]++;
c[y]++;
edge[id[n+1][y]].flow--;
edge[id[n+1][y]^1].flow++;
edge[id[x][m+1]].flow--;
edge[id[x][m+1]^1].flow++;//这个点必要,恢复原状
return 0;
}
}
int main()
{
memset(head,-1,sizeof(head));
cin>>n>>m;
s=n+m+1;
t=s+1;
for(int i=1;i<=n;i++) cin>>r[i];
for(int i=1;i<=m;i++) cin>>c[i];
for(int i=1;i<=n;i++)
{
id[i][m+1]=cnt+1;
add(s,i,r[i]);
for(int j=1;j<=m;j++)
{
if(i==1)
{
id[n+1][j]=cnt+1;
add(j+n,t,c[j]);
}
id[i][j]=cnt+1;
add(i,j+n,1);
}
}//建图,初始化id数组
for(int i=1;i<=n;i++) r[i]=m-r[i];//注意这里r和c数组已经被替换成0的数量
for(int i=1;i<=m;i++) c[i]=n-c[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cout<<1-check(i,j);//对于每个点判断
cout<<endl;
}
}
```