B Black and white
题目链接:
题目大意:
用题目所给算法给你一个矩阵,将一个点涂黑的花费为该点上的值,如果一个2*2的区域内已经有了三个位置被涂黑,涂黑第四个位置不需要代价,问涂黑整个矩阵的最小代价。
题解:
可以证明涂黑整个矩阵只需要首先涂黑n+m-1个点,当然这n+m-1个点需要达到某种条件,即任意一个点所在的行和列都存在被涂黑的点。
以下几个图符合条件:
下面这个不符合条件,因为存在某个位置的行列都没有涂黑:
如果直接对于矩阵来思考最小值比较困难,不如换个思路:
可以用矩阵来表示两个点连边的代价(如p[i][j]=k表示将点i与点j建立链接需要的代价),如果这样想的话上面几个图就会有另一种解释方式。
这里的意思是从某个边开始,一定可以在同行或者同列找到下一个边
(如果某种涂黑的方案是有效解的话,那么上面的每一个边都可以通过这种方式链接到所有边,换句话说就是构造一个最小代价的联通图,链接矩阵轴上的每一个点,矩阵的轴上一共有n+m个点,而我们需要n+m-1条边,这样就不难想到这题需要最小生成树)
经过上面的思考,这题的基本模型就出来了:
给你n*m条边,找出联通n+m个点的最小权值,嗯~非常经典的最小生成树
由于这题数据量比较大,据说会卡掉使用sort的克鲁斯卡尔算法,这里提供一种不用sort来用克鲁斯卡尔的方法 (你就不会用prim?抱歉,我和他不熟)
多亏了本题的数据输入使用题目给的代码,说实话我还没见过这种出数据的方式,把大量题目数据运算交给选手的电脑跑,算法岂是如此不便之物!,我们可以直接在输入数据的时候就让其按照升序存储。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+5;
const int inf=0x3f3f3f3f;
int a[N*2];
int fid(int k)
{
if(a[k]==k) return k;
else return a[k]=fid(a[k]);
}
vector< pair<int ,int > > e[100005];//存储权值对应的边,如e[2]{3,5}表示从点3到点n+5有一条权值为2的边供你选择。
signed main()
{
int n,m,x,b,c,d,p;
cin>>n>>m>>x>>b>>c>>d>>p;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
x=(x*x*b+x*c+d)%p;
e[x].push_back(make_pair(i,n+j));//因为点有n+m个,但是题目给的是i,j,所以对于每个j,使其对应第n+j个点
}
for(int i=1;i<=n+m;i++) a[i]=i;//并查集初始化
int ans=0,cnt=0;
for(int i=0;i<p;i++)//直接从小到大枚举权值,省去了sort,真是
{
for(int j=0;j<e[i].size();j++)
{
int x=fid(e[i][j].first),y=fid(e[i][j].second);
if(x!=y)
{
cnt++;
a[y]=x;
ans+=i;
}
}
if(cnt==n+m-1) break;
}
cout<<ans<<endl;
return 0;
}