2021牛客多校第三场题解
B题
题目大意:n * m的方格,每个方格的值表示把这个方格涂黑的代价,2 * 2的方格内如果有三个已经涂黑,那么第四个自动涂黑,即代价为0.问将所有方格涂黑的最小代价。n,m<5000
思路:可以发现,只需要涂n+m+1个方格就可以将整个图涂黑。将w[i,j]看作i和j两个点之间的边权,转化为最小生成树问题,在n*m条边中选出n+m条边构成最小生成树。因为是稠密图所以选择prim算法。注意如果行编号从1 ~ n,则列编号从n+1 ~ n + m,避免边的表示重复。
ac代码:
#include <bits/stdc++.h> using namespace std; const int N = 1e4+5; typedef long long LL; bitset<N>vis; int d[N*2]; int w[N][N]; LL ans = 0; int n,m,a,b,c,D,p; int main(){ cin >> n >> m >> a >> b >> c >> D >> p; memset(d,0x3f,sizeof d); memset(w,0x3f,sizeof w); // for(int i = 0;i < n+m;i++)cout << vis[i]<<" "; // for(int i = 0;i < n*m;i++)w[i] = ((LL)a*a*b+(LL)a*c+d)%p,a = w[i]; for(int i = 0;i < n;i++) for(int j = n;j < n+m;j++) w[i][j] = w[j][i] = ((LL)a*a*b+(LL)a*c+D)%p,a = w[i][j]; d[1] = 0; // for(int i = 0;i < n;i++) // for(int j = n;j < n+m;j++)cout << w[i][j]<<" \n"[j==n+m-1]; for(int i = 0;i < n+m;i++){ int t = -1; for(int j = 0;j < n+m;j++) if(!vis[j]&&(t==-1||d[j]<d[t]))t = j; vis[t] = true; ans+=d[t]; for(int j = 0;j < n+m;j++)d[j] = min(d[j],w[t][j]); } cout << ans << endl; return 0; }
p.s. 比较考察分析能力的题,对最小生成树的掌握程度增加了!
F题
题目大意:给出n,m。类似于24点,要求n个数(1 ~ 13之间)用加减乘除凑出m,输出所有可行的n个数。并且运算中必须出现分数。n<=4,m<=1e9
思路:发现n<=3时不存在解。n=4时,按顺序枚举两两数之间四则运算操作,搜索时flag和cnt维护有无分数出现,来判断当前解是否可行。
ac代码:
#include <bits/stdc++.h> using namespace std; const int N = 1e5+5; int n,m,flag,cnt,idx; vector<double>v,e[N]; bool ck(double x,double y){ if(x-(int)x>1e-8||y-(int)y>1e-8||x/y-(int)(x/y)>1e-8)return 1; return 0; } void dfs2(int y,vector<double>v){ int len = v.size(); if(len == 1){ if(fabs(v[0] - m) < 1e-8){ flag++; if(y)cnt++; } } for(int i = 0;i < len;i++) for(int j = 0;j < len;j++){ if(i == j)continue; vector<double>t; t.clear(); for(int k = 0;k < len;k++) if(k != i&&k != j)t.push_back(v[k]); t.push_back(v[i]+v[j]); dfs2(y,t); t.pop_back(); t.push_back(v[i]-v[j]); dfs2(y,t); t.pop_back(); t.push_back(v[i]*v[j]); dfs2(y,t); t.pop_back(); t.push_back(v[i]/v[j]); dfs2(y|ck(v[i],v[j]),t); t.pop_back(); } } bool check(vector<double>v){ flag = cnt = 0; dfs2(0,v); if(flag == cnt&&cnt)return 1; return 0; } void dfs1(int len,int s){ if(len == n + 1){ if(check(v))e[idx++] = v; return ; } for(int i = s;i <= 13;i++){ v.push_back(i); dfs1(len+1,i); v.pop_back(); } } int main(){ cin >> n >> m; if(n <= 3){ cout << "0" << endl; } else{ dfs1(1,1); cout << idx << endl; for(int i = 0;i < idx;i++) for(int j = 0;j < e[i].size();j++) cout << e[i][j] << " \n"[j == e[i].size()-1]; } return 0; }
C题
题目大意:空白的n * n矩阵,需要在其中m个位置填数字,给出每行、每列的最大值a[1],a[2]......a[n],b[1],b[2],......b[n]。问填满后矩阵所有元素的和最小是多少(矩阵元素非负)。n <= 2e3,m <= 8e5。
思路:理想情况为,某一行和某一列的最大值相等,那么只需要在交点填这个最大值就可以同时满足行最大值和列最大值。m个位置中,可能有多个点坐标满足a[x] = b[y],那么此题就转化成行和列构成二分图求最大匹配。ans = 行权值和 + 列权值和 - 最大匹配权值和。
ac代码:
#include <bits/stdc++.h> using namespace std; const int N = 1e6+6; int n,m,k; int match[N],e[N],ne[N],h[N],idx; bool st[N]; int b[N],c[N]; void add(int a,int b){ e[idx] = b,ne[idx] = h[a],h[a] = idx++; } bool find(int u){ for(int i = h[u];i != -1;i = ne[i]){ int j = e[i]; if(!st[j]){ st[j] = true; if(!match[j]||find(match[j])){ match[j] = u; return true; } } } return false; } int main(){ cin >> n >> m >> k; memset(h,-1,sizeof h); int ans = 0; 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]; while(m--){ int p,q; cin >> p >> q; if(b[p] == c[q])add(p,q); } for(int i = 1;i <= n;i++)memset(st,0,sizeof st),find(i); for(int i = 1;i <= n;i++) if(match[i])ans-=c[i]; cout << ans << endl; return 0; }
注意 :因为match[i]表示第i列有没有匹配到,所以减去的时候应减去对应列的值。