2021牛客暑期多校训练营3 部分题解
B.Black and white
-
题意
给你一个 n × m n\times m n×m的矩阵,初始时每个单元格式白色的,你需要将这染成黑色的,当对一个单元格 ( i , j ) (i,j) (i,j)染色需要花费 c i j c_{ij} cij。而当一个四个单元格形成的正方形中有 3 3 3个染色了,那么剩下的一个会自动染色,不用花费。问你最小花费是多少。 -
解题思路
我们将行列转换为点,那么每个格子 ( i , j ) (i,j) (i,j)染色就可以看成是第 i i i行和第 j j j列连了一条边,那么当出现四个正方形的时候即表示其中 i , i + 1 , j , j + 1 i,i+1,j,j+1 i,i+1,j,j+1中有 3 3 3个点连通,那么我们扩大思考,要将整个大正方形染色,就要将这 n 行 m 列 n行m列 n行m列连通,所以我们不妨想到最小生成树。对于题目数据,由于边规模太大,如果我们先建边再排序就会超时,好在边权并不是很大,所以我们可以使用桶排序,最后使用 K r u s k a l Kruskal Kruskal算法即可。 -
AC代码
/**
*@filename:B
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-07-24 12:02
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5000 + 5;
const int P = 1e9+7;
int n,m;
int b,c,d,p;
int pre,cur;//代表a[i - 1]和a[i];
//将行列转换为点。其中格子(i,j) -> 边i->j
struct edge{
int u,v;
};
vector<edge> edges[100005];//利用桶排序,由于边太多了。
int father[N << 1];
int find(int x){
int r = x;
while(father[r] != r){
r = father[r];
}
int i = x, j;
while(father[i] != r){
j = father[i];
father[i] = r;
i = j;
}
return r;
}
void solve(){
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= m; ++ j){
cur = (1LL * pre * pre * b + 1LL * pre * c + d) % p;
edges[cur].push_back({i,n + j});
pre = cur;
}
}
for(int i = 1; i <= n + m; ++ i){
father[i] = i;
}
ll ans = 0,cnt = 0;
for(int i = 0; i <= 100000; ++ i){
for(auto &edge : edges[i]){
int fu = find(edge.u), fv = find(edge.v);
if(fu != fv){
father[fu] = fv;
ans += i;
cnt ++;
}
if(cnt == n + m - 1){
break;
}
}
}
cout << ans << endl;
}
int main(){
cin >> n >> m >> pre >> b >> c >> d >> p;
solve();
return 0;
}
C.Minimum grid
-
题意
给你一个 n × n n\times n n×n的矩阵,初始是空白的,但给出了每行每列的最大值。现在你需要对 m m m个点赋值,使得它们之和是最小的。求最小和。 -
解题思路
按照贪心思想,我们知道,当不考虑行与列的限制关系时,对于每一行每一列我们只需填一个最大值,剩下的全填 0 0 0则可以。那么如果考虑它们的关系,如果一个最大值又在 i , j i,j i,j列,即它们值是相同,那么我们是可以放在交叉点使他们共享了。所以这实际上就是一个行与列的二分图匹配问题,我们将行与列转换为点,对相同的最大限定值建立边使用匈牙利算法匹配即可。 -
AC代码
/**
*@filename:C
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-07-26 20:42
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2000 + 5;
const int P = 1e9+7;
int n,m,k;//m表示m个位置可以填整数。
int b[N],c[N];
bool vis[N];//vis[i]表示i当前是否已配对
int match[N];//match为匹配数组,。
ll ans;//ans代表最小花费。初值为b和c的总和。
vector<int> g[N];//建图。
bool dfs(int u){
for(auto &v : g[u]){
if(!vis[v]){
//如果当前没有被确定。
vis[v] = true;
if(!match[v] || dfs(match[v])){
//如果没有被匹配或者可以谦让。
match[v] = u;
return true;
}
}
}
return false;
}
void solve(){
int u,v;//坐标点,看成是两点顶点相连。
while(m -- ){
scanf("%d%d", &u, &v);
if(b[u] == c[v]){
g[u].push_back(v);//对行建立于列的边。
}
}
//即建立最大匹配。
for(int i = 1; i <= n; ++ i){
memset(vis,0,sizeof(vis));
dfs(i);
}
for(int i = 1; i <= n; ++ i){
if(match[i]){
ans -= c[i];
}
}
printf("%lld\n",ans);
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++ i){
scanf("%d", &b[i]);
ans += b[i];
}
for(int i = 1; i <= n; ++ i){
scanf("%d", &c[i]);
ans += c[i];
}
solve();
return 0;
}
E.Math
-
题意
给你一个整数 n n n,求多少对数 ( x , y ) (x,y) (x,y)满足 x y + 1 ∣ x 2 + y 2 xy+1|x^2+y^2 xy+1∣x2+y2,其中 1 ≤ x ≤ y ≤ n 1\leq x\leq y\leq n 1≤x≤y≤n。 -
解题思路
不得不说,这道题出的是真好。花了好多时间才搞懂。我们来看:
由于 ( x y + 1 ) ∣ x 2 + y 2 (xy+1)|x^2+y^2 (xy+1)∣x2+y2,则 x 2 + y 2 = k ( x y + 1 ) x^2+y^2=k(xy+1) x2+y2=k(xy+1),其中 1 ≤ x ≤ y ≤ n 1\leq x\leq y \leq n 1≤x≤y≤n。
我们可以固定 k k k和 y y y,即把它们视作常数,那么转换一下就是 x 2 − k y x + y 2 − k = 0 x^2-kyx+y^2-k=0 x2−kyx+y2−k=0,根据韦达定理方程的解 x 1 , x 2 x_1,x_2 x1,x2满足:
x 1 + x 2 = k y , x 1 x 2 = y 2 − k x_1+x_2=ky,x_1x_2=y^2-k x1+x2=ky,x1x2=y2−k。
根据这个我们可以先试着思考一下, k , y k,y k,y已经为整数,那么如果 x 1 x_1 x1为偶数,说明 x 2 x_2 x2也为偶数,那么另一个解实际上也出来了,我们再倒过去求 k k k即可。
即我们先构造一个解 x 1 = a , y 1 = a 3 x_1=a,y_1=a^3 x1=a,y1=a3,其中 a a a为偶数,且 a > 1 a>1 a>1,对于 a = 1 a=1 a=1的情况我们特判即可,根据韦达定理计算只有一种。则根据方程 a 2 + a 6 = k ( a 4 + 1 ) a^2+a^6=k(a^4+1) a2+a6=k(a4+1),故易知 k k k取 a 2 a^2 a2。
那么根据韦达定理可知 x 2 = k y 1 − x 1 x_2=ky_1-x_1 x2=ky1−x1,而由于 x 1 ≤ y 1 x_1\leq y_1 x1≤y1, k > 1 k>1 k>1,故易知 x 2 > y 1 x_2>y_1 x2>y1的,这对 ( x 2 , y 1 ) (x_2,y_1) (x2,y1)解是不符合 x ≤ y x\leq y x≤y的,而由于 x x x和 y y y的系数相同,所以我们可以转换一下构造解为 ( y 1 , x 2 ) (y_1,x_2) (y1,x2),那么这个一定符合条件,然后再通过这个利用韦达定理循环构造解,直到 y y y超过范围。
这个 a a a我们通过枚举即可,由于 y = a 3 , y ≤ 1 e 18 y=a^3,y\leq1e18 y=a3,y≤1e18,所以 a ≤ 1 e 6 a\leq 1e6 a≤1e6。我们枚举预处理即可。注意,我们只要存储 y y y即可,最后二分求 ≤ n \leq n ≤n,即 y ≤ n y\leq n y≤n,故 ≤ n \leq n ≤n的都满足答案。
要注意,由于此题会爆long long,所以我们要用 _ _ i n t 128 \_\_int128 __int128来存储,需要使用快读快写来对该类型进行输入输出。 -
AC代码
/**
*@filename:E
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-07-29 10:37
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;
template<typename T>void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9){
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T> void read(T &x)
{
x = 0;
char ch = getchar();
ll f = 1;
while(!isdigit(ch)){
if(ch == '-')
f *= -1;
ch = getchar();
}
while(isdigit(ch)){
x = x * 10 + ch - 48;
ch = getchar();
}
x *= f;
}
vector<__int128> result;
int t;
__int128 n;
void init(){
result.push_back(1);//x = 1,y = 1,只有一种情况。
//注意,这里long long存不下。要使用__int128
for(__int128 a = 2; a <= 1e6; ++ a){
//先列出构造的解,再不断递推。
__int128 x = a, y = a * a * a, k = a * a;
while(y <= 1e18){
result.push_back(y);
__int128 temp = k * y - x;
x = y;
y = temp;
}
}
sort(result.begin(),result.end());
}
void solve(){
write(upper_bound(result.begin(),result.end(),n) - result.begin());
putchar('\n');
}
int main(){
init();
read(t);
while(t -- ){
read(n);
solve();
}
return 0;
}
F.24dian
-
题意
给你 n ( 1 ≤ n ≤ 4 ) n(1\leq n\leq 4) n(1≤n≤4)张牌,每张牌取值范围为 [ 1 , 13 ] [1,13] [1,13],现在需要你对这些牌进行加减乘除运算凑成 m m m,其中运算过程中必须出现一次非整数的分数。 -
解题思路
由于数据量不大,我们直接爆搜,注意条件限制,运算过程中必须出现一次非整数的分数,即我们必须进行一次除法,且进行除法后我们还要判断得到的结果是不是分数。处理好这些细节即可。 -
AC代码
/**
*@filename:F
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-07-24 12:49
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000000 + 5;
const int P = 1e9+7;
const double eps = 1e-6;
int n,m;
int ans[N][5],tot;
//只有分数解的解法。
//思路,去枚举n张牌。可以通过dfs来枚举。
//dfs,求解这n张牌
double num[5];
int flag;//判断方案是否可行。
void dfs2(int idx,int state){
if(flag == 3)return;//说明方案已经不可行了。
if(idx == n){
if(fabs(num[1] - m) <= eps){
if(state){
//说明遇到了非整数分数。
flag = 1;
}
else{
flag = 3;
}
}
return;
}
double temp[5];
for(int i = 0; i <= n; ++ i){
temp[i] = num[i];
}
for(int i = 1; i <= n - idx + 1; ++ i){
for(int j = 1; j <= n - idx + 1; ++ j){
//枚举两个数运算。
if(i == j)continue;
for(int op = 0; op < 4; ++ op){
int nstate = state;
if(op == 0)num[i] += num[j];
else if(op == 1)num[i] -= num[j];
else if(op == 2)num[i] *= num[j];
else{
num[i] /= num[j];
//判断是否出现小数。
if(fabs(num[i] - ((int)num[i])) > eps){
nstate = 1;
}
}
//为了避免j干扰。将j放到后面的闲置位置。
swap(num[j],num[n - idx + 1]);
dfs2(idx + 1,nstate);
//还原a。
for(int i = 1; i <= n; ++ i){
num[i] = temp[i];
}
}
}
}
}
void dfs1(int step,int k){
//还需要构建k个数。
if(step > n){
flag = 0;
dfs2(1,0);//判断构建的数得到的答案是否符合要求。
if(flag == 1){
tot ++;
for(int i = 1; i <= n; ++ i){
ans[tot][i] = num[i];
}
}
return;
}
for(int i = k; i <= 13; ++ i){
num[step] = i;
dfs1(step + 1,i);
}
}
void solve(){
dfs1(1,1);//构建数。
cout << tot << endl;
for(int i = 1; i <= tot; ++ i){
for(int j = 1; j <= n; ++ j){
cout << ans[i][j] << " ";
}
cout << endl;
}
}
int main(){
cin >> n >> m;
//当n<=3的情况是不可能的,因为要出现一次非整数,然后又要变成m。
solve();
return 0;
}
J.Counting Triangles
- 题意
给你 n n n个顶点的无向完全图,其中每条边都有一个权值,为 1 1 1代表黑边,为 0 0 0代表白边。问等边三角形的个数有多少,即三个顶点之间连成的边都是相同颜色的。 - 解题思路
我们先来计算一下,等边三角形的个数有多少个,即我们可以从 n n n个顶点中选出 3 3 3个顶点构成三角形,所以方案数是 C n 3 C_n^3 Cn3。如果我们直接处理这道题会非常棘手,所以我们可以反过来求不是等边三角形的个数,而我们又发现一个神奇的特性,若不是等边三角形,则其中存在一对异色,即二白一黑或者二黑一白,所以我们可以统计每个点的白边和黑边,那么相乘则是每个点的贡献,最后注意除以 2 2 2,因为每个点在其他地方也计算了一次。 - AC代码
/**
*@filename:J
*@author: pursuit
*@csdn:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-07-24 12:11
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;
namespace GenHelper
{
unsigned z1,z2,z3,z4,b,u;
unsigned get()
{
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1; return res;
}
void srand(int x)
{
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
bool edge[8005][8005];
int main() {
int n, seed;
cin >> n >> seed;
srand(seed);
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
edge[j][i] = edge[i][j] = read();
ll ans = 0;
for(int i = 0; i < n; ++ i){
ll x = 0,y = 0;
for(int j = 0; j < n; ++ j){
if(edge[i][j])x ++;
else y ++;
}
ans += x * y;
}
//Cn3
cout << 1LL * n * (n - 1) * (n - 2) / 6 - ans / 2 << endl;
return 0;
}

浙公网安备 33010602011771号