首页 > 其他分享 >UNR#3 Day2

UNR#3 Day2

时间:2024-05-30 16:03:43浏览次数:14  
标签:UNR fa int 复杂度 Day2 vis base mathcal

A. 白鸽

是否有解即判欧拉回路,判每个结点的度数是否都是偶数和有边的的结点是否与 \(1\) 号结点连通即可。

因为最后形成的是一个封闭图形,大可把 \(x\) 轴正方向看做一条射线,我们每由上至下穿过这条射线会对答案产生 \(1\) 的贡献,反之,从下至上穿过这条射线就会对答案产生 \(-1\) 的贡献。

问题转化为给欧拉回路上的无向边定向,使其在仍是一个欧拉回路的使答案最大。

先钦定所有 \(y\) 轴右侧穿过 \(x\) 轴的的边的方向都是由上至下,即所有边的方向是顺时针,然后初步统计一个圈数出来,这个圈数不小于答案。

然后考虑给边重定向,即有些边需要反向,也即把边分成两部分,一部分维持原样,一部分反向,且仍满足欧拉回路的要求,其中 \(y\) 轴右侧的边反向后需付出 \(2\) 的代价(原来 \(+1\),现在 \(-1\)),使付出的代价最小。

这么说其实做法就显然了,就是在求一个最小费用最小割,费用流即可。

具体地,对 \(y\) 轴右侧穿过 \(x\) 轴的边,建流量为 \(1\),费用为 \(2\) 的 反边,其余的边建流量为 \(1\),费用为 \(0\) 的边。统计每个网络中结点的度数(入度为 \(ind_i\),出度为 \(oud_i\))。

建超级源、汇点 \(S, T\),枚举每个结点,若当前结点 \(i\) 满足:

  • \(ind_i < oud_i\),连边 \(S \to i\),流量为 \(\dfrac{oud_i - ind_i}2\),费用为 \(0\)。
  • \(oud_i > ind_i\),连边 \(i \to T\),流量为 \(\dfrac{ind_i - oud_i}2\),费用为 \(0\)。

类似地,因为边反向后会使点的入度和出度同时发生变化,所以流量带了一个 \(\dfrac12\) 的系数。

建出图跑 Dinic 即可,因为是单位容量网络,Dinic 的时间复杂度是 \(\mathcal O(m^\frac34)\)。

又因为费用只有 \(-2, 0, 2\) 两种,很容易维护 SPFA 队列的有序,此时的 SPFA 的时间复杂度近似于线性,即 \(\mathcal O(m)\)。

所以总的时间复杂度就是 \(\mathcal O(m^\frac74)\)。

代码 :

#include <bits/stdc++.h>

using namespace std;

constexpr int N = 2e4 + 10, S = N - 1, T = N - 2, INF = 0x3f3f3f3f;

int n, m, x[N], y[N], c[N], d[N];
bool vis[N];

namespace DSU {
    int fa[N];
    inline void init(int n) {iota(fa + 1, fa + n + 1, 1);}
    inline int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
    inline void merge(int x, int y) {fa[find(y)] = find(x);}
}

int tot = 1, head[N];
struct Edge {int to, nxt, w, c;} e[N << 2];
inline void add(int u, int v, int w, int c) {
    e[++tot] = Edge{v, head[u], w, c}, head[u] = tot;
    e[++tot] = Edge{u, head[v], 0, -c}, head[v] = tot;
}

int dinic() {
    int totfl = 0, totc = 0;

    deque<int> q;

    auto spfa = [&]() -> bool {
        memset(c, 0x3f, sizeof(c)), c[S] = 0, q.emplace_back(S);
        while (!q.empty()) {
            int u = q.front(); q.pop_front();
            vis[u] = 0;
            for (int i = head[u], v; v = e[i].to, i; i = e[i].nxt) if (e[i].w && c[u] + e[i].c < c[v]) {
                c[v] = c[u] + e[i].c;
                if (!vis[v]) vis[v] = 1, (e[i].c > 0 ? q.emplace_back(v) : q.emplace_front(v));
            }
        }
        return c[T] < INF;
    };

    auto dfs = [&](auto &&self, int u, int cf) -> int {
        if (u == T) return cf;
        vis[u] = 1; int rf = 0;
        for (int i = head[u], v; v = e[i].to, i; i = e[i].nxt) if (!vis[v] && e[i].w && c[v] == c[u] + e[i].c) {
            int tf = self(self, v, min(cf, e[i].w));
            if (tf) {
                cf -= tf, rf += tf;
                e[i].w -= tf, e[i ^ 1].w += tf;
                if (!cf) break;
            }
        }
        vis[u] = 0; if (!rf) c[u] = INF;
        return rf;
    };

    memset(vis, 0, sizeof(vis));
    while (spfa()) totc += dfs(dfs, S, INF) * c[T];
    return totc;
}

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
    DSU::init(n);
    int ans = 0;
    while (m--) {
        int i, j; cin >> i >> j;
        vis[i] = vis[j] = 1; DSU::merge(i, j);
        if (1ll * x[i] * y[j] > 1ll * x[j] * y[i]) swap(i, j);
        if (y[i] >= 0 && y[j] < 0) add(i, j, 1, 2), ans++;
        else add(i, j, 1, 0);
        d[i]--, d[j]++;
    }
    for (int i = 1; i <= n; i++) if (vis[i] && (DSU::find(i) != DSU::find(1) || d[i] & 1)) {cout << "-1"; return 0;}
    for (int i = 1; i <= n; i++) d[i] < 0 ? add(S, i, -d[i] >> 1, 0) : add(i, T, d[i] >> 1, 0);
    cout << ans - dinic() << '\n';
    return 0;
}

B. 百鸽笼

记 \(m = \max a_i\)。

留一个不住总感觉怪怪的,干脆把题意转化为 \(\sum a_i\) 个人住,求每列最后一个住满的概率。

容斥,需要求每列在某些列前住满的概率。

设当前列要在某 \(k\) 列住满前住满,那么其余列如何我们都不关心,可以直接忽略。

问题来到对于一个序列,其中 \(i\) 出现次数为 \(a_i\),\(j\)(代表“某些列”)出现次数 \(< a_j\),且 \(i\) 最后出现,设其长度为 \(l\),求其对答案的贡献。

由条件,该序列上每一个点都有 \(m + 1\) 种选择(因为第 \(i\) 列是第一个填满的,填的过程中每一列都不满,都能填),所以它对答案的贡献就是 \(\left(\dfrac1{k + 1}\right)^l\)。

DP。

设 \(f_i\) 表示序列长度为 \(i\) 时对答案的贡献,枚举每一列上住了多少人来转移。

因为同一列的选择间是等价的,所以若当前列选了 \(t\) 个人,则对 \(f_i\) 的贡献要乘上 \(\dfrac1{t!}\),最后每个 \(f_i\) 再乘上 \(i!\)。

DP 的时间复杂度是 \(\mathcal O(nm^2)\)。

需要枚举的是“某 \(k\) 列”和最后填满的列,所以总时间复杂度是 \(\mathcal O(2^nn^2m^2)\)。

枚举“某 \(k\) 列”的 \(\mathcal O(2^n)\) 过于朴素,实际上我们并不关心究竟选出来了哪些列,所以可以把选中的列数写进状态里,每一列根据选或不选来转移即可,DP 的时间复杂度是 \(\mathcal O(n^3m^2)\)。

我们同样需要枚举最后填满的列,所以总时间复杂度是 \(\mathcal O(n^4m^2)\)。

非常跑不满,所以实现好的话是可过的,但是离稳过还差点。

枚举最后填满的列也是多余的,把填满的列也归到“某 \(k\) 列”中,最后对 \(\mathcal O(n^2m)\) 个状态 \(\mathcal O(nm)\) 枚举每一列的状态算答案即可,时间复杂度 \(\mathcal O(n^3m^2)\)。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

constexpr int N = 40, M = 910, MOD = 998244353;

int n, m, nm, a[N];
ll fct[M], inv[M], ifct[M], f[N][M], g[N][M];

ll qp(ll base, int e) {
    ll res = 1;
    while (e) {
        if (e & 1) res = res * base % MOD;
        base = base * base % MOD;
        e >>= 1;
    }
    return res;
}

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], m += a[i];

    fct[0] = fct[1] = ifct[0] = ifct[1] = inv[1] = 1;
    for (int i = 2; i <= m; i++) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
    for (int i = 2; i <= m; i++) fct[i] = fct[i - 1] * i % MOD, ifct[i] = ifct[i - 1] * inv[i] % MOD;

    m = 0, f[0][0] = 1;
    for (int i = 1; i <= n; m += a[i++]) {
        for (int j = i - 1; j >= 0; j--) {
            for (int k = m; k >= 0; k--) if (f[j][k]) {
                for (int l = 0; l < a[i]; l++) {
                    f[j + 1][k + l] = (f[j + 1][k + l] + f[j][k] * ifct[l]) % MOD;
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        ll ans = 0;
        for (int j = 0; j < n; j++) {
            for (int k = 0; k <= m; k++) if (g[j][k] = f[j][k]) {
                if (j) for (int l = max(0, k - a[i] + 1); l <= k; l++) g[j][k] = (g[j][k] - g[j - 1][l] * ifct[k - l]) % MOD;
                if (g[j][k]) ans = (ans + g[j][k] * ((j & 1) ? -1 : 1) * fct[k + a[i] - 1] % MOD * qp(inv[j + 1], k + a[i])) % MOD;
            }
        }
        cout << (ans * ifct[a[i] - 1] % MOD + MOD) % MOD << ' ';
    }
    return 0;
}

C. 鸽举选仕

标签:UNR,fa,int,复杂度,Day2,vis,base,mathcal
From: https://www.cnblogs.com/chy12321/p/18222541

相关文章

  • 【WEEK14】 【DAY2】Shiro Part 7【English Version】
    2024.5.28TuesdayContinuationfromprevious【WEEK14】【DAY1】ShiroPart6【EnglishVersion】Contents15.8.IntegrateShirowithThymeleaf15.8.1.Modifypom.xmltoAddDependencies15.8.1.1.Importingtheshiro-thymeleafIntegrationPackage15.8.1.2.......
  • [CEOI2010 day2] pin
    [CEOI2010day2]pin题目信息题目链接LuoguP6521题目描述给定\(n\)个长度为\(4\)的字符串,你需要找出有多少对字符串满足恰好\(D\)个对应位置的字符不同。输入格式输入第一行两个整数\(n,D\)。接下来的\(n\)行,每行一个长度为\(4\)的字符串。输出格式输出一行......
  • UNR#3 Day1
    A.鸽子固定器把固定器按\(s\)排序。如果选的个数\(<m\),选出来的一定是一个连续段,否则再多选夹在中间的固定器一定优。如果选的个数\(=m\),按牢固度从小到大考虑每一个固定器,找以当前固定器为最小牢固度的长度为\(m\)的最优序列,分讨几种情况之后不难发现忽略牢固度更小......
  • 代码随想录算法训练Day20|LeetCode654-最大二叉树、LeetCode617-合并二叉树、LeetCode
    最大二叉树题目描述力扣654-最大二叉树给定一个不重复的整数数组nums。最大二叉树可以用下面的算法从nums递归地构建:创建一个根节点,其值为nums中的最大值。递归地在最大值左边的子数组前缀上构建左子树。递归地在最大值右边的子数组后缀上构建右子树。......
  • Cesium4Unreal - # 002 线图元绘制
    文章目录基础点绘制1思路2步骤2.1创建一个自定义组件2.2重写CreateSceneProxy方法2.3实现自定义的场景代理类2.4在场景代理类中实现绘制逻辑2.5使用自定义组件3代码实现3.1c++代码3.1.1自定义组件代码MyPrimitivePointComponent.hMyPri......
  • 【刷题笔记Day2】数组|977.有序数组的平方、209. 长度最小的子数组、59.螺旋矩阵II
    文章目录977.有序数组的平方解题思路遇到的问题及解决方案209.长度最小的子数组解题思路遇到的问题及解决方案59.螺旋矩阵II解题思路遇到的问题及解决方案总结977.有序数组的平方题目描述:给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新......
  • 【Linux】icmp_seq=1 Destination Host Unreachable
    执行ping命令提示:From192.168.XX.XX  icmp_seq=1DestinationHostUnreachable这个错误消息通常表示以下几种情况之一:网络连接问题:目标主机可能没有连接到网络,或者网络中的某个路由器无法将数据包转发到目标主机。目标主机不存在:目标主机的IP地址可能不存在,或者......
  • 卡尔的算法训练营day2,数组2
    第一题做错了,还是边界值的问题。忘记存草稿了。题号997publicstaticintfindJudge(intn,int[][]trust){int[]judgeCandidate=newint[n+1];int[]othersCandidate=newint[n+1];for(inti=0;i<trust.length;i++){//二维数组......
  • 【Unreal】虚幻GAS系统快速入门
    【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!前言最近在用UE做单机ARPG的战斗系统,研究了一下GAS。本文主要介绍GAS各个模块的用途,以及特定功能的多种实现方法。为了让大部分人能快速上手,不会涉......
  • MounRiver Studio打开文件乱码
    一、问题背景1.UTF-8编码的文件打开始终是被识别成GBK编码2.要更改FileProperties才能强制让文件以UTF-8显示3.但是会在工程路径和workspace路径下生成单独的配置文件,并且所有文件都要重复操作二、解决方法根本原因是MounRiverStudio有一个自动检测编码的插件,每次打......