这一场是把前两场的图论都出完了么
B Black and white[图论]
这道题曾经校赛出过一个很类似的,散播弗兰三三的花瓣啥的,当时学长给的标答是并查集,牛逼队友也快乐地用并查集排个序就过掉了这题
#include <cstdio>
#include <algorithm>
#define int long long
const int MAXN = 5e3 + 5;
int n, m, a, b, c, d, p, ans;
struct NODE
{
int id;
int val;
};
inline bool operator < (const NODE& lhs, const NODE& rhs)
{
return lhs.val < rhs.val;
}
NODE A [MAXN * MAXN];
int fa [MAXN << 1]; // col: 1 ~ n, row: n + 1 ~ n + m
inline int find (int x)
{
return fa [x] == x ? x : fa [x] = find (fa [x]);
}
inline bool merge (int x, int y)
{
x = find (x);
y = find (y);
if (x != y)
return fa [x] = y, true;
return false;
}
inline bool merge (const NODE& node)
{
int x = (node.id - 1) / m + 1;
int y = (node.id - 1) % m + 1 + n;
return merge (x, y);
}
signed main ()
{
scanf ("%lld%lld%lld%lld%lld%lld%lld", &n, &m, &a, &b, &c, &d, &p);
A [0].val = a;
for (int i = 1; i <= n * m; i ++)
A [i].val = (A [i - 1].val * A [i - 1].val * b + A [i - 1].val * c + d) % p, A [i].id = i;
for (int i = 1; i <= n + m; i ++)
fa [i] = i;
std::sort (A + 1, A + n * m + 1);
for (int i = 1; i <= n * m; i ++)
if (merge (A [i]))
ans += A [i].val;
printf ("%lld\n", ans);
}
然后出题人表示这是一个套路题。行和列为点,把网格抽象成一个完全二分图,问题变为选权值最小的边使得二分图连通,此时方格的四个角在连三条边的情况下就连通了。
所以是一个最小生成树,完全图所以就prim了
#include<iostream>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
typedef long long ll;
const int N = 5e3+10;
const int INF = 0x3f3f3f3f;
int cost[N][N];
int A[N*N],dis[N+N],vis[N+N];
int n,m,a,b,c,d,mod;
ll prim(){
memset(dis,0x3f,sizeof dis);
ll ans = 0;
dis[1] = 0;vis[1] = 1;
int last=1;
for(int i=1;i<n+m;i++){
if(last <= n){
for(int j=1;j<=m;j++){
dis[j+n]=min(dis[j+n],cost[last][j]);
}
}
if(last > n){
for(int j=1;j<=n;j++) {
dis[j]=min(dis[j],cost[j][last-n]);
}
}
int minn = INF, pos = -1;
for(int j=1;j<=n+m;j++){
if((!vis[j]) && minn > dis[j]){
pos = j;
minn = dis[j];
}
}
ans += minn;
last = pos;
vis[pos] = 1;
}
return ans;
}
signed main() {
cin>>n>>m>>a>>b>>c>>d>>mod;
A[0]=a;
for(int i=0;i<=n*m;i++)
A[i+1]=(A[i]*A[i]*b+A[i]*c+d)%mod;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++){
cost[i][j] = A[m*(i-1)+j];
}
}
cout<<prim();
}
C Minimum grid[图论]
先将所有能填的格子都填上,再考虑有哪些是重复了可以去掉的。
我们称 b i b_i bi和 c i c_i ci 为⾏限定数和列限定数,统称限定数.
考虑最⼤的那个限定数,不妨设为 x x x ;找到⾏、列限定数是 x x x 的那些⾏和列组成的⼦矩形.
显然,其他数都不能满⾜ x x x的要求.
于是我们把每⾏建⼀个点,每列建⼀个点,可以填数的位置建⼀条边,得到⼀个⼆分图;这个⼆分图的最⼤匹配就是我们能节省出的 x x x的个数.
这样把每个限定数的⼆分图都建出来跑⼀遍匹配,算⼀下贡献即可
然而实现起来很简单,在整个图构造的二分图里,只要最后在匹配里那么这个匹配对应的行就可以节约一个列,因此直接对行匹配,算贡献就行了
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 2e3+5;
vector<int> mp[N];
int b[N],c[N],n,m,k,link[N],vis[N];
int dfs(int x){
for (auto v:mp[x]){
if(vis[v]) continue;
vis[v] = 1;
if (link[v] == 0 || dfs(link[v])){
link[v] = x;
return 1;
}
}
return 0;
}
int main() {
ll ans = 0;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>b[i];
ans += b[i];
}
for(int i=1;i<=n;i++){
cin>>c[i];
ans += c[i];
}
for(int i=0,u,v;i<m;i++){
cin>>u>>v;
if(b[u] == c[v]){
mp[u].push_back(v);
}
}
for(int i=1 ;i<= n; i++){
memset(vis,0,sizeof vis);
ans -= b[i]*dfs(i);
}
cout<<ans<<endl;
return 0;
}