20231115

2023/11/15

(AtCoder Beginner Contest 302) 补题

A.签到

B.枚举

C.可以next_permution ,我用的是dfs枚举,每次枚举排列的时候直接把可以判断不合法的跳过的枚举会好写一下

D.简单二分

E.可以从每条边只会被加入,删除,执行两次操作入手,那么复杂度就是合理的O(logn*n),数组sz来记录当前点的度数大小,只有0和1的时候会改变当前的ans。开set来维护与当前点连边的集合

F - Merge Set(补)

赛时想过最短路,结果不会建图,光是建图就是O(n*n).

思路:在一个集合中的点,我们两两建1的边权,表明通过一个元素从一个集合到另外一个集合的代价为1,那么根据这个思路。我们的建图时间是不合理的。然后我们从1为起点跑最短路,最后最短路长度减一就是答案,因为1集合的第一次代价应该为0,但是我们算了1

技巧:对于每个集合,建立一个虚拟源点(编号为总点数+当前集合编号),虚点指向集合内的点0的边权,集合内的点指向虚点的边权为1.这个是O(n)的建图,等价于两两连边

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define Acode ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define int long long
const int N = 1e6 + 10;
vector<pair<int, int>> g[N];
int vis[N];
int dis[N];
void dij()
{
    for (int i = 1; i <= 1e6; i++)
        dis[i] = 1e9;
    dis[1] = 0;
    // memset(dis, 0x3f, sizeof dis);
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
    q.push({0, 1});
    while (q.size())
    {
        int u = q.top().second;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (auto it : g[u])
        {
            int v = it.first;
            int w = it.second;
            if (dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                q.push({dis[v], v});
            }
        }
    }
}

void solve()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        for (int j = 1; j <= x; j++)
        {
            int u;
            cin >> u;
            g[m + i].push_back({u, 0});
            g[u].push_back({m + i, 1});
        }
    }
    dij();
    if (dis[m] == 1e9)
    {
        cout << -1 << endl;
        return;
    }
    cout << dis[m] - 1 << endl;
}

signed main()
{
    Acode;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

下午组队训练,题目待补,感觉质量还行

Codeforces Round 904 (Div. 2) D - Counting Rhyme(补)

思路:非常经典的模型,如果两个数都可以被x整除,那么他们的gcd也一定可以被x整除。换言之,只有他们的gcd能整除x才是不好的,反之就是ok的。

所以问题就转化成了求数组的对数的gcd个数。我们用cnt桶计数的思想来记录每个数出现的个数,那个对于一个i数来说,gcd等于i的对数为,f[i] * f([i]-1)/2,f数组代表i的倍数的数的个数。这样子乘我们会得到一些数对的gcd为i,一些为i的倍数。减去即可。因为s[i]记录的就是准确的gcd为i的对数,不重不漏。

最后算答案的时候,我们可以先知道总对数,然后来判断一个gcd的对数他们是否合法。方法就是看一下有没有这个gcd的因子出现过。在我们统计i的倍数的数的个数,可以开一个vis数组来|= 实现当前这个数是否出现过他的因子

补充:由于要遍历值域,所以数组元素的大小不应超过1e6!

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define Acode ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define int long long
const int N = 2e6 + 10;
int a[N];
int s[N], f[N];
int cnt[N];
int vis[N];
void solve()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cnt[i] = 0, f[i] = 0, vis[i] = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        cnt[a[i]]++;
        vis[a[i]] = 1;
    }
    int ans = n * (n - 1) / 2;
    for (int i = n; i >= 1; i--)
    {
        for (int j = i; j <= n; j += i)
        {
            f[i] += cnt[j];
            vis[j] |= vis[i];
        }
    }
    for (int i = n; i >= 1; i--)
    {
        int sum = (f[i] - 1) * f[i] / 2;
        for (int j = 2 * i; j <= n; j += i)
        {
            sum -= s[j];
        }
        s[i] = sum;
    }
    for (int i = 1; i <= n; i++)
    {
        if (vis[i])
            ans -= s[i];
    }
    cout << ans << endl;
}

signed main()
{
    Acode;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-11-15 21:25  ヤ﹎句号悠灬  阅读(22)  评论(0)    收藏  举报