ABC392 赛后总结

ABC392

本蒟蒻的第一篇博客,就从 ABC 开始吧!

D

D 这道题花的时间实在是太久了。这种数学题,我们就推式子。我们先看暴力做法,就是枚举两个 Dice \(i\)\(j\),然后得出它们相等的概率,具体来说,即:

\(\sum_{k = 1}^{10^5} \frac{C_{i, k}}{K_i} \times \frac{C_{j, k}}{K_j}\)\(C_{i, j}\) 表示的是第 \(i\) 个 Dice 的有 \(j\) 数字的面的数量)

我们发现这个做法的时间复杂度是 \(O(n^2V)\) 的(\(V\) 即最大的元素),因此需要考虑优化。而这道题给出了一个条件 \(\sum_{i = 1}^n K_i \le 10^5\),因此我们可以考虑从枚举 \(V\) 到枚举每个 \(i\) 所拥有的数字,再和前面去作比较,时间复杂度就变成了 \(O(n\sum_{i = 1}^n K_i)\),计算量在 \(10^7\) 这个数量级,可以过。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <cmath>
#include <time.h>
#define x first
#define y second 

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;

const int N = 1e5 + 10, INF = 0x3f3f3f3f;

int n;
int k[N];
vector<int> ps[110];
double po[110][N], cu[N];

int main()
{
	// freopen("contest.in", "r", stdin);
	// freopen("contest.out", "w", stdout);

	scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &k[i]);
        int x;
        for (int j = 1; j <= k[i]; j ++ )
        {
            scanf("%d", &x);
            ps[i].push_back(x);
        }
    }
    for (int i = 1; i <= n; i ++ ) sort(ps[i].begin(), ps[i].end());
    double ans = 0;
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j < i; j ++ ) cu[j] = 0;
        for (int j = 0; j < k[i]; j ++ )
        {
            int u = j, x = ps[i][j];
            while (u <= k[i] && ps[i][j] == ps[i][u]) u ++ ;
            int len = u - j;
            double pos = (len * 1.0 / k[i]);
            for (int l = 1; l < i; l ++ ) cu[l] += po[l][x] * pos;
            po[i][x] = pos;
            j = u - 1;
        }
        for (int j = 1; j < i; j ++ ) ans = max(ans, cu[j]);
    }
    printf("%.10lf\n", ans);
    
	cerr << clock() << endl;

	return 0;
}

E

据说这道题是最不板的一道题。

这道题目,由于和连通性有关,我们自然而然想到并查集。可以发现,在每个连通块中,有些边可以删去,不妨叫做无用边。怎么求这些无用边呢?我们可以用并查集做出一颗生成树,树边算作有用,非树边算作无用。那么我们就把这个问题转化成了连通块中的非树边应当连接到哪里去。利用贪心的想法,我们从更多非树边的连通块连向非树边更少的连通块,这样子按照非树边数量排好序,在遍历到每个连通块时,就一定会与前面连好了的大连通块连接,因此可以放心地连到下一个连通块中。时间复杂度是 \(O(n\log n)\)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <cmath>
#include <time.h>
#define x first
#define y second 

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 2e5 + 10, INF = 0x3f3f3f3f;

int n, m;
struct Edge
{
    int u, v;
} e[N];
int fa[N], idx[N];
bool st[N], h[N];
vector<int> head;
vector<PII> es[N];

int find(int x)
{
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

int f(int x)
{
    return lower_bound(head.begin(), head.end(), x) - head.begin();
}

bool cmp(int a, int b)
{
    return es[a].size() > es[b].size();
}

int main()
{
	// freopen("contest.in", "r", stdin);
	// freopen("contest.out", "w", stdout);

	scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) fa[i] = i;
    int cnt = n;
    for (int i = 1; i <= m; i ++ )
    {
        scanf("%d%d", &e[i].u, &e[i].v);
        int p = find(e[i].u), q = find(e[i].v);
        if (p != q)
        {
            cnt -- ;
            fa[p] = q;
            st[i] = true;
        }
    }
    printf("%d\n", cnt - 1);
    for (int i = 1; i <= n; i ++ ) 
    {
        int p = find(i);
        h[p] = true; 
    }
    for (int i = 1; i <= n; i ++ )
        if (h[i])
            head.push_back(i);
    sort(head.begin(), head.end());
    for (int i = 0; i < head.size(); i ++ ) idx[i] = i;
        
    for (int i = 1; i <= m; i ++ )
        if (!st[i])
        {
            int p = f(find(e[i].u));
            es[p].push_back({e[i].u, i});
            st[i] = true;
        }
    
    sort(idx, idx + head.size(), cmp);
    
    cnt = 0;
    for (int i = 0; i < head.size(); i ++ )
    {
        for (auto j : es[idx[i]])
        {
            int id = j.second;
            cnt ++ ;
            if (cnt >= head.size()) break;
            printf("%d %d %d\n", id, j.first, head[idx[cnt]]);
        }
    }
    
	cerr << clock() << endl;

	return 0;
}

F

这道题我们如果按照顺序考虑,那就是要维护一个数据结构,支持按排名插入、排名查询,时间复杂度不能太高。那么当然是平衡树啊。

但是平衡树实在是太难写了,我们来看,正难则反嘛,看一下反面。假设这个数组是有 \(n\) 个位置,然后我们往里面填数。我们发现第 \(n\) 次填数直接就是填在 \(P_n\) 这个位置上,第 \(n - 1\) 次填数时,不看这个元素,填在剩余位置中的第 \(P_{n - 1}\) 个位置中……这样下去,我们发现操作就只用求剩余位置中的第 \(P_i\) 个位置,我们不看这个元素,其实也就是相当于把后面位置的排名全部 \(-1\)。单点修改,维护前缀(排名也相当于前缀),查询的话可以进行二分。这样就可以用树状数组二分或树状数组 + 二分来得到了。时间复杂度是 \(O(n\log^2 n)\)\(O(n\log n)\)

#include <iostream>
#include <algorithm>
#include <vector>
#include <time.h>
#define x first
#define y second 

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 5e5 + 10, INF = 0x3f3f3f3f;

int n;
int tr[N];
int p[N];
int ans[N];

int lowbit(int x)
{
	return x & -x;
}

void add(int a, int c)
{
	for (int i = a; i <= n; i += lowbit(i)) tr[i] += c;
}

int sum(int a)
{
	int res = 0;
	for (int i = a; i; i -= lowbit(i)) res += tr[i];
	return res;
}

int main()
{
	// freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);

	scanf("%d", &n);
	for (int i = 1; i <= n; i ++ )
		scanf("%d", &p[i]);

	for (int i = 1; i <= n; i ++ ) add(i, 1);
	for (int i = n; i >= 1; i -- )
	{
		int l = 1, r = n; 
		while (l < r)
		{
			int mid = l + r + 1 >> 1;
			if (sum(mid) <= p[i]) l = mid;
			else r = mid - 1;
		}
		ans[r] = i;
		add(r + 1, -1);
	}
	for (int i = 1; i <= n; i ++ ) printf("%d ", ans[i]);
	puts("");
		
	cerr << clock() << endl;

	return 0;
}
posted @ 2025-02-09 21:12  SteveHans  阅读(60)  评论(0)    收藏  举报