哎哎,感觉是很典的题啊,但还是不会。
一些无脑的转化
首先转化成二维数组,原题中 \(2n\) 个方程相当于必须满足每一行和每一列的数之和是定值,已被选的数可以让这个位置的行与列的总和分别减去这个数,然后直接令它等于 \(0\),显然这是与原条件等价的。
另外我们可以发现有些位置的集合的填数是独立的。
如图,红色格子的填数与蓝色格子的填数无关,因此,我们可以将原问题变成若干的子问题。具体的,如果第 \(i\) 行的第 \(j\) 列可以填,那么我们可以从第 \(i\) 行向第 \(j\) 列连一条边,每个子问题构成一个连通块,显然存在一个子问题中 \(\sum{a_i} \neq \sum{b_i}\) 则必然无解。
然后呢,感觉有点无从下手了,我们可以发现:一个连通块的限制肯定比一颗树的限制严格宽泛,所以我们可以考虑一棵生成树怎么构造。我们可以发现如果一个点的度数为 \(1\) 那么它的实际含义就是原图中一行或一列中只有一个数可以填,那么它一定是唯一确定的,因此我们可以对这棵树拓扑排序,每次删除节点,然后新增度数为 \(1\) 的节点,我们可以发现,如果有解,那么一定可以用这种方法构造出来一组解。
AC code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define N 2005
int read(){int x=0,f=0;char ch=getchar();while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return f?-x:x;
}
int n,m,a[N],b[N],ans[N][N],fa[N<<1],tvis[N<<1],in[N<<1];
bool vis[N][N];
vector<pii>E[N<<1],G[N<<1];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){int fx=find(x),fy=find(y);
}
vector<int>now;
void dfs(int p){ tvis[p]=1,now.push_back(p);for(auto &&i:E[p])if(!tvis[i.fi]){G[i.fi].push_back({p,i.se}); in[i.fi]++;G[p].push_back({i.fi,i.se}); in[p]++;dfs(i.fi);}
}
void bfs(){if(now.size()==1){now.pop_back();return;}queue<int>q;for(auto &&i:now)if(in[i]==1)q.push(i);while(q.size()){int u=q.front(); q.pop();tvis[u]=2;if(!in[u])continue;for(auto &&i:G[u]){int v=i.fi,x=i.se,tx=(x+n-1)/n,ty=(x%n)?x%n:n;if(tvis[v]==2)continue;ans[tx][ty]=(u<=n?a[u]:b[u-n]); a[tx]-=ans[tx][ty],b[ty]-=ans[tx][ty];if(--in[v]==1)q.push(v);}} now.clear();
}
void solve(){n=read(),m=read();for(int i=1;i<=n;i++)a[i]=read();for(int j=1;j<=n;j++)b[j]=read();for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)vis[i][j]=ans[i][j]=0;for(int i=1;i<=n<<1;i++)fa[i]=i,E[i].clear(),G[i].clear(),tvis[i]=in[i]=0;while(m--){int x=read(),tx=(x+n-1)/n,ty=(x%n)?x%n:n;vis[tx][ty]=1,ans[tx][ty]=read();}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(vis[i][j])a[i]-=ans[i][j],b[j]-=ans[i][j];else merge(i,j+n),E[i].push_back({j+n,(i-1)*n+j}),E[j+n].push_back({i,(i-1)*n+j});for(int i=1;i<=2*n;i++)if(find(i)==i)dfs(i),bfs();for(int i=1;i<=n;i++)if(a[i]||b[i]){puts("No Solution");return;}puts("OK");for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)printf("%lld ",ans[i][j]); puts("");
}
signed main(){int T=read();while(T--)solve();return 0;
}
一些无用的总结
做构造题时,如果条件过于宽泛,我们可以考虑自己额外添加一些条件,从特殊情况出发。