牛客小白月赛23

比赛链接
出题人题解

A-膜法记录

知识点:贪心+枚举+二进制状态压缩+预处理
题意:一个$n \times m$的01矩阵,行操作可使某一行变为全0,列操作可使某一列变为全0,问能否使用少于a次行操作和少于b次列操作得到全0矩阵。$1\leq n \leq20,1\leq m \leq {10^5}$。
贪心:先使用行操作使得存在非0的列数尽可能的少。
枚举:枚举每一行是否使用行操作,复杂度${2^n}$。对于一种行操作情况$x$,假设消去了$g(x)$列,则还需要$m-g(x)$次列操作。此题的关键就在于如何去枚举以及如何快速求得$g(x)$值。
二进制状态压缩:把每一列从下往上看成一个二进制数,则数的范围在$[0,{2^n-1}]$之间。假设$f(x)$表示值为$x$的列出现了$f(x)$次,则$g(x)=\sum_{i=0}^{x}{f(i)},\quad i\bigodot x=x$。
总结:此题在赛中完全没思路。一是没注意到数值范围,二是没有想过枚举这方面,总的来说对二进制状态压缩不敏感。

假设一个$4\times6$的矩阵如下所示

c1c2c3c4c5c6
001110
100001
011111
000011
$f(2)$$f(4)$$f(5)$$f(13)$$f(14)$
11211

举例:行操作为13(1101),则上图的第2、3、4、5都被置为全0,$g(13)=f(4)+f(5)=3$。

预处理

  • 初始化$f(x)、g(x)$均为0.
  • 先遍历每一列,得到$f(x)$。
  • 遍历每一行$i$,如果$x\&(1<<i)=0$,则$g(y)+=f(x),\quad y=x|(1<<i)$。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e5 + 117;
const int MAXM = 1e5 + 117;

int T;
int n, m, a, b, maxnum;
char s[27][MAXN];
int cnt[1100017];
int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d%d%d", &n, &m, &a, &b);
        for(int i = 0; i < n; i++) scanf("%s", s[i]);

        maxnum = 1 << n;
        for(int i = 0; i < maxnum; i++) cnt[i] = 0;
        for(int j = 0; j < m; j++) {
            int t = 0;
            for(int i = 0; i < n; i++) {
                if(s[i][j] == '*') t |= (1 << i);
            }
            cnt[t]++;
        }

        for(int i = 0; i < n; i++) {
            for(int j = 0; j < maxnum; j++) {
                if((j & (1 << i)) == 0) cnt[j | (1 << i)] += cnt[j];
            }
        }

        bool pr = false;
        for(int i = 0; i < maxnum; i++) {
            if(__builtin_popcount(i) <= a && m - cnt[i] <= b) {
                pr = true;
                break;
            }
        }

        if(pr) puts("yes");
        else puts("no");
    }
    return 0;
}

B-阶乘

知识点:二分+质因分解
二分:若$n!\%p=0$,则有$(n+1)!\%p=0$,答案具有单调性,可二分。
质因分解:若$n!$中的质因子个数都不小于$p$的质因子个数,则$n!$是$p$的倍数。
总结:赛中想到了二分和质因分解,但是不会阶乘质因分解,遂卒。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e5 + 117;
const int MAXM = 1e5 + 117;

int T;
int p;
int prime[MAXN];
void getPrime(int n) {
    memset(prime, 0, sizeof(prime));
    for(int i = 2; i <= n; i++) {
        if(!prime[i]) prime[++prime[0]] = i;
        for(int j = 1; j <= prime[0] && prime[j] <= n / i; j++) {
            prime[prime[j]*i] = 1;
            if(i % prime[j] == 0) break;
        }
    }
}
int fatCnt;
int factor[117][2];
void getFactors(int x) {
    fatCnt = 0;
    int tmp = x;
    for(int i = 1; prime[i] <= tmp / prime[i]; i++) {
        if(tmp % prime[i] == 0) {
            factor[fatCnt][0] = prime[i];
            factor[fatCnt][1] = 0;
            while(tmp % prime[i] == 0) {
                factor[fatCnt][1]++;
                tmp /= prime[i];
            }
            fatCnt++;
        }
    }
    if(tmp != 1) {
        factor[fatCnt][0] = tmp;
        factor[fatCnt++][1] = 1;
    }
}
bool check(int n) {
    for(int i = 0; i < fatCnt; i++) {
        int cnt = 0, t = n;
        while(t) {//阶乘质因分解
            cnt += t / factor[i][0];
            t /= factor[i][0];
        }
        if(cnt < factor[i][1]) return false;
    }
    return true;
}
int erfen() {
    int l = 1, r = p;
    while(l <= r) {
        int mid = (l + r) / 2;
        if(check(mid)) r = mid - 1;
        else l = mid + 1;
    }
    return l;
}
int main() {
    getPrime(100000);
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &p);
        getFactors(p);
        printf("%d\n", erfen());
    }
    return 0;
}

C-完全图

知识点:完全图+贪心+二分+__int128
完全图:$n$个点的完全图有$\tbinom{n}{2}$条边,逆向思维:有$n$个孤立的点添加$\tbinom{n}{2}-m$条边得到连通块最少。
贪心:除非必须连接孤立点,否则在连通块内部加边。
二分:完全图的边数随点数单调递增,可二分判断。
__int128:$n$达到$10^{18}$数量级,long long不足以存放(小声吐槽出题人居然py)。
总结:赛中并没有想到逆向思维,所以当时纠结了一下怎么删才能连通块最多。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e5 + 117;
const int MAXM = 1e5 + 117;

int T;
__int128 n, m;
inline __int128 read() {
    __int128 x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
inline void print(__int128 x) {
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}
void erfen() {
    __int128 l = 1, r = n;
    while(l <= r) {
        __int128 mid = (l + r) / 2;
        if(mid * (mid - 1) / 2 >= m) r = mid - 1;
        else l = mid + 1;
    }
    print(n - l + 1);
    putchar(10);
}
int main() {
    scanf("%d", &T);
    while(T--) {
        n = read();
        m = read();
        m = n * (n - 1) / 2 - m;
        erfen();
    }
    return 0;
}

D-病毒传染

高能数学题,不会。。。

E-A+B问题

知识点:补码
补码:在计算机系统中,数值一律用补码来表示和存储,在逻辑上,最大值和最小值是紧挨着的,如下图所示,这就会有最大值+1=最小值、最小值-1=最大值。故任给$a、c$均有$b=c-a,min \leq b \leq max$,所以答案一定是4294967296。
总结:想了半小时才琢磨出来,大佬都是秒A,对细节掌握的太菜了。
补码

F-美丽的序列I

暂未领悟

G-树上求和

知识点:贪心+计数
贪心:所有路径中出现次数多的边贪心权值小的颜色。
计数:朴素的做法是枚举所有的路径统计每条边出现的次数,然而复杂度不能被接受。假设边$i$的左边有$l(i)$个点,则边$i$的右边有$n-l(i)$个点,边在所有路径中出现的总次数为$l(i)*(n-l(i))$。只需要一次dfs即可求出所有的$l(i)$。
总结:赛后看自己的代码发现边当成有向边也过了,可能是数据水了也可能是自己对题意把握的不够准确,需警惕无向边的加边。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e6 + 117;
const int MAXM = 2e5 + 117;

int n;
struct Edge {
    int to, next;
    LL cnt;
} edge[MAXN];
bool vis[MAXN];
int head[MAXN], tot;
void init() {
    tot = 0;
    memset(head, -1, sizeof(head));
    memset(vis, false, sizeof(vis));
}
void addedge(int u, int v) {
    edge[tot].to = v;
    edge[tot].cnt = 0;
    edge[tot].next = head[u];
    head[u] = tot++;
}
LL dfs(int id) {
    vis[id] = true;
    LL ret = 1;
    for(int i = head[id]; i != -1; i = edge[i].next) {
        if(vis[edge[i].to]) continue;
        LL num = dfs(edge[i].to);
        edge[i].cnt = num * (n - num);
        ret += num;
    }
    return ret;
}
bool cmp(Edge a, Edge b) {
    return a.cnt > b.cnt;
}
int main() {
    init();
    scanf("%d", &n);
    for(int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    dfs(1);
    sort(edge, edge + tot, cmp);
    LL ans = 0;
    for(int i = 1; i < n; i++) ans += i * edge[i - 1].cnt;
    printf("%lld\n", ans);
    return 0;
}

H-奇怪的背包问题增加了

知识点:贪心
贪心:虚假的背包问题,实则是贪心。因为物品的体积全为2的次幂,所以应该优先选体积大的。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e6 + 117;
const int MAXM = 2e5 + 117;

int T, n;
char s[MAXN];
struct node {
    int x;
    int id;
} a[MAXN];
bool cmp(node a, node b) {
    if(a.x != b.x) return a.x > b.x;
    return a.id < b.id;
}
int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        for(int i = 0; i < n; i++) {
            scanf("%d", &a[i].x);
            a[i].id = i;
        }

        sort(a, a + n, cmp);
        int sum = 1 << 30, num;
        for(int i = 0; i < n; i++) {
            num = 1 << a[i].x;
            if(sum >= num) sum -= num, s[a[i].id] = '1';
            else s[a[i].id] = '0';
        }

        if(sum) puts("impossible");
        else puts(s);
    }
    return 0;
}

I-寻找子串

知识点:后缀+枚举
后缀:假设子串$[i,j]$为字典序最大的子串,则满足$j=n-1$,即字符串的后缀。
枚举:字符串长度不超过1000,$O(n^2)$的枚举都能AC。
总结:string的使用相当不熟练,也是因为平时用的少。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e6 + 117;
const int MAXM = 2e5 + 117;

string s, ans;
int main() {
    cin >> s;
    while(!s.empty()) {
        if(ans < s) ans = s;
        s.erase(0, 1);
    }
    cout << ans << endl;
    return 0;
}

J-最大的差

小白赛温暖的签到题。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f;     ///1 061 109 567
const int negative_infinite = 0xcfcfcfcf;   ///-808 464 433
const double pi = acos(-1);
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int MAXN = 1e6 + 117;
const int MAXM = 2e5 + 117;

int n;
int a[MAXN];
int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    sort(a, a + n);
    printf("%d\n", a[n - 1] - a[0]);
    return 0;
}
Last modification:March 29th, 2020 at 01:43 am
如果觉得我的文章对你有用,请随意赞赏