「PMOI-5」奇怪的方程 题解
哎哎,感觉是很典的题啊,但还是不会。
一些无脑的转化
首先转化成二维数组,原题中 \(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;
}
一些无用的总结
做构造题时,如果条件过于宽泛,我们可以考虑自己额外添加一些条件,从特殊情况出发。