牛客3.7总结

A题

最小圆覆盖板子题

第二个题解

神神奇奇,看上去是o(n^3)的复杂度,but实际上均摊复杂度为 o(n)

const int MAXN = 5e5 + 10;
const double eps = 1e-12;
struct Point{
    double x, y;
    double  operator - (const Point &b) {
        return (x * x - b.x * b.x) - (b.y * b.y - y * y) ;
    }
}a[MAXN];
double ansr;
Point o;
int n;
double dis(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

void new_r(Point p1, Point p2, Point p3) {
    double a, b, c, d, e, f;
    a = p2.y - p1.y;
    b = p3.y - p1.y;
    c = p2.x - p1.x;
    d = p3.x - p1.x;
    e = p2 - p1;
    f = p3 - p1;
    o.x = (a * f - b * e) / (2 * a * d - 2 * b * c);
    o.y = (d * e - c * f) / (2 * a * d - 2 * b * c);
    ansr = dis(o, p1);
}

void mincircle() {
    //随机增量,将数据打乱,不然会WA
    random_shuffle(a + 1,a + n + 1);
    o = a[1];
    ansr = 0;
    for(int i = 2; i <= n; i++) {
        if(dis(a[i], o) > ansr + eps) {
            o = a[i];
            ansr = 0;
            for(int j = 1; j < i; j++) {
                if(dis(o, a[j]) > ansr + eps) {
                    o.x = (a[i].x + a[j].x) / 2;
                    o.y = (a[i].y + a[j].y) / 2;
                    ansr = dis(o, a[j]);
                    for(int k = 1; k < j; k++){
                        if(dis(o,a[k]) > ansr + eps){
                            new_r(a[i], a[j], a[k]);
                        }
                    }
                }
            }
        }
    }
}
/**
for(int i = 1; i <= n; i++) scanf("%lf%lf", &a[i].x, &a[i].y);
**/

B题

题意

给出n个单词,找最大集合使得不存在一个单词交换一对字母等于集合中的其它单词

题解

求最大独立集,建二分图,可以变换的两个单词建边(互斥的)

建图用dfs,即从一个点出发,如果将这个点标记为1,它流经过的点就应该标记为-1,这样保证网络流的方向

最大独立集 = n (点数) - 最大流

#include <bits/stdc++.h>
#define SZ(v) (int)v.size()

const int INF = 1e9;
const int MAXN = 2e4 + 10;
const int MAXM = 2e5 + 10;

namespace MaxFlow {
    struct Edge {
        int v, rev, f;
        Edge(int v, int rev, int f):v(v),rev(rev),f(f){}
    };

    int n, s, t;
    int cur[MAXM], dep[MAXN], gap[MAXN];
    int flow;
    std::vector<Edge> G[MAXN];

    void add_edge(int u, int v, int f) {
        G[u].push_back(Edge(v, SZ(G[v]), f));
        G[v].push_back(Edge(u, SZ(G[u]) - 1, 0));
    }

    int dfs(int u, int lim) {
        if (u == t) return lim;
        int num = 0, f;
        for (int &i = cur[u], v; i < SZ(G[u]); ++i) {
            if (dep[v = G[u][i].v] == dep[u] - 1 && (f = G[u][i].f))
                if (G[u][i].f -= (f = dfs(v, std::min(lim - num, f))),
                        G[v][G[u][i].rev].f += f, (num += f) == lim)
                    return num;
        }
        if (!--gap[dep[u]++]) dep[s] = n + 1;
        return ++gap[dep[u]], cur[u] = 0, num;
    }

    void init(int _n) {
        n = _n;
        for (int i = 0; i < n; ++i) G[i].clear();
    }

    void solve(int _s, int _t) {
        s = _s, t = _t, flow = 0;
        for (int i = 0; i <= n; ++i) cur[i] = dep[i] = gap[i] = 0;
        for (gap[0] = n; dep[s] <= n; flow += dfs(s, INF));
    }
}

using MaxFlow::add_edge;

const int MAX = 5e2 + 10;
char a[MAX][30];
bool judge(char *a, char *b) {
    int diff = 0;
    for(int i = 0; i < strlen(a); i++) {
        if(a[i] != b[i]) diff++;
    }
    if(diff == 2) return 1;
    else return 0;
}

std::vector<int> G[MAX];
int tag[MAX];//标记
void dfs(int i, int flag) {
    if(tag[i] != 0) return;
    tag[i] = flag;
    for(int j = 0; j < G[i].size(); j++)
        dfs(G[i][j], -flag);
}

int main() {
    int N;
    scanf("%d", &N);
    for(int i = 1; i <= N; i++) {
        scanf("%s", a[i]);
    }
    for(int i = 1; i <= N; i++) {
        for(int j = 1; j <= N; j++) {
            if(judge(a[i], a[j])) {
                G[i].push_back(j);
            }
        }
    }
    for (int i = 1; i <= N; i++) {
        if (tag[i] == 0) dfs(i, 1);
    }

    int s = N + 1, t = s + 1;
    MaxFlow::init(t + 1);
    for(int i = 1; i <= N; i++) {
        if(tag[i] == 1)   add_edge(s, i, 1);
        else   add_edge(i, t, 1);
    }
    for(int i = 1; i <= N; i++) {
        for(int j = 1; j <= N; j++) {
            if(judge(a[i], a[j])) {
                if(tag[i] == 1) add_edge(i, j, 1);
                else add_edge(j, i, 1);
            }
        }
    }
    MaxFlow::solve(s, t);
    printf("%d\n", N - MaxFlow::flow);
}

C题

题意

定义运算a⊗b为a乘b无进位的结果,现在给一个数n,找到最小的a,使得a⊗a等于n

题解
(先看数据范围,n的长度最大为25,其实a的位数最大就是13,由复杂度想算法
还有可以模拟一下,发现n一定是奇数
(唉,看懂了题目的演算过程,也不要只顾及自己翻译过来的方法,容易陷进去思维误区,还要注意题目的式子!!!)
dfs(复杂度到达10^25)+剪枝,在每一个位子上实际上达到要求的很少,不会到达10^25

#include <bits/stdc++.h>
typedef long long ll;
using  namespace std;
const int MAX = 27;
int ans[MAX];
bool flag;
int len;
int a[MAX];

void dfs(int pos) {
    if(flag) return;
    if(pos >= len) {//找到最小的一个答案了,标记flag,结束所有的递归
        flag = 1;
        for(int i = 0; i <= len / 2; i++) cout << ans[i];
        cout << endl;
        return;
    }
    for(int i = 0; i < 10; i++) {
        ans[pos] = i;
        int res = 0;
        for(int j = 0; j <= pos; j++) {
            res += (ans[j] * ans[pos - j]) % 10;
            res %= 10;
        }
        if(res == a[pos])
            dfs(pos + 1);
    }
}

int main() {
    string str;
    cin >> str;
    len = str.length();
    for(int i = 0; i < len ; i++) {
        a[i] = str[i] - '0';
    }
    if(len % 2 == 0) {
        printf("-1\n");
        return 0;
    }
    flag = 0;
    dfs(0);
    if(!flag) printf("-1\n");
}

E题

题意

给出n个数,找出最小的字典序且包含所有1到k的序列

题解

从后面看,显然每个最后一个出现的数字,如果再这之前没有比它更小的,就只能选这个。last数组标记一下

从标记的某个数之前的所有数,找到比这个数小的字典序最小的上升子序列(栈优化),加入,挤掉后面出现的数字

例如 1   4   3   5   4   1   2   4   3 1\ 4\ 3 \color{red}{\ 5} \color{black}{\ 4} \color{red}{\ 1\ 2\ 4\ 3} 1 4 3 5 4 1 2 4 3

5之前的最小的为1 3

1之前的最小的为4,比1大,跳过

$ \color{red}{好叭,上面的思路又码炸了,but一定是可行的,zyx是用队列+结构体做的,也是可行的} $

类似的题解

新的思路:如果没有k这个要求的话,求子序列,相当于单调栈;

而这道题目相当于要求,只要一个数没有出现在单调栈内(vis[a[i]] = 0),那么当遇到最后一个数的时候,没的选,只能选上了,即小于下标小于(i < last[a[i]])才可以操作

#include <bits/stdc++.h>
typedef long long ll;
using  namespace std;
const int MAX = 2e5 + 10;
int vis[MAX];
int a[MAX];
int last[MAX];
int ans[MAX];

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        last[a[i]] = i;
    }
    int end = 0;
    for(int i = 1; i <= n; i++) {
        if(vis[a[i]] == 0) {
            while(i < last[ans[end]] && end && ans[end] > a[i]) {
                vis[ans[end]] = 0;
                end--;
            }
            vis[a[i]] = 1;
            end++;
            ans[end] = a[i];
        }
    }
    for(int i = 1; i <= end; i++) {
        printf("%d ", ans[i]);
    }
}

G题

题意

n个人,分成m组,(a[0], a[1],…a[m - 1])

问每个组内生日相同,每个组之间生日不同的概率(一年规定365天)

要取log10

题解

总的种类是 36 5 n 365^n 365n

先把人分到不同的组里(因为同一个组生日相同)

先把n个人分成n块,先在地理位置上把前a[i]个看成一组(这样分法有 A n n A_{n}^{n} Ann)由于在一个组内排列组合是一样的(eg, 组的大小为3,元素为1,2,3,实际上123 = 132 =…)

所以上面的分法实际为 A n n / ( A a [ 0 ] a [ 0 ] × A a [ 1 ] a [ 1 ] . . . A a [ m − 1 ] a [ m − 1 ] ) A_{n}^{n} / (A_{a[0]}^{a[0]} \times A_{a[1]}^{a[1]}... A_{a[m - 1]}^{a[m - 1]}) Ann/(Aa[0]a[0]×Aa[1]a[1]...Aa[m1]a[m1])

上面把人分好了,现在把生日分给组,即 A 365 n A_{365}^{n} A365n

此时是讲m个组都看成不一样的了,实际上会有人数一样的组还要除以每种人数的组数的阶乘

(其实按照 C s u m a [ 0 ] × C s u m − a [ 0 ] a [ 1 ] . . . C_{sum}^{a[0]} \times C_{sum -a[0]}^{a[1]}... Csuma[0]×Csuma[0]a[1]...答案是和上面一样的

由于取log10,所以上面所有的乘法变加法,除法变减法

#include <bits/stdc++.h>
typedef long long ll;
using  namespace std;
const int MAXN = 400 + 10;
int a[MAXN];
int tot[MAXN];

int main() {
    int n;
    scanf("%d", &n);
    int sum = 0;
    for(int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        tot[a[i]]++;
        sum += a[i];
    }
    double ans = sum * -1 * log10(365);
    for(int i = 365; i > 365 - n; i--) ans += log10(i);
    for(int i = 1; i <= sum; i++) {
        ans += log10(i);
    }
    for(int i = 0; i < n; i++) {
        for(int j = 1; j <= a[i]; j++) {
            ans -= log10(j);
        }
    }
    for(int i = 1; i <= 365; i++) {
        if(tot[i]) {
            for(int j = 1; j <= tot[i]; j++) {
                ans -= log10(j);
            }
        }
    }
    printf("%.10lf\n", ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值