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列 nm连通,所以我们不妨想到最小生成树。对于题目数据,由于边规模太大,如果我们先建边再排序就会超时,好在边权并不是很大,所以我们可以使用桶排序,最后使用 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+1x2+y2,其中 1 ≤ x ≤ y ≤ n 1\leq x\leq y\leq n 1xyn

  • 解题思路
    不得不说,这道题出的是真好。花了好多时间才搞懂。我们来看:
    由于 ( 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 1xyn​。
    我们可以固定 k k k​和 y y y​,即把它们视作常数,那么转换一下就是 x 2 − k y x + y 2 − k = 0 x^2-kyx+y^2-k=0 x2kyx+y2k=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=y2k
    根据这个我们可以先试着思考一下, 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=ky1x1​​,而由于 x 1 ≤ y 1 x_1\leq y_1 x1y1 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 xy的,而由于 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,y1e18,所以 a ≤ 1 e 6 a\leq 1e6 a1e6。我们枚举预处理即可。注意,我们只要存储 y y y即可,最后二分求 ≤ n \leq n n,即 y ≤ n y\leq n yn,故 ≤ 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(1n4)张牌,每张牌取值范围为 [ 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;
}
posted @ 2022-03-26 16:48  unique_pursuit  阅读(55)  评论(0)    收藏  举报