2021-11-21 19:56阅读: 660评论: 0推荐: 1

2021ICPC沈阳站

2021ICPC沈阳站

B. Bitwise Exclusive-OR Sequence

题意

对于长度为 n 的序列,给出 m 个关系,第 i 个关系形如 ajak=wi ,表示第 j 个元素和第 k 个元素的异或值为 wi ,问满足所有关系的条件下,这个序列的和最小为多少。如果无解,输出 1

分析

对于关系 ajak=wi ,把 j 点和 k 点连一条边,边权为 wi

无解的情况下,明显一定是存在一个环,在环内各边权存在矛盾。对于每一个连通块,当我们确定其中一个点的点权,其他点的点权是确定的。

判断是否无解,我们只需要看边权是否矛盾,可以 dfs 每个连通块,如果某个点的点权和邻接点的点权异或值不为边权,则无解。

对于有解的情况,枚举每个连通块的每一个位,对于其中一个点(初始点)可以为 01 。那么在某一位上我们只需要取这个连通块内所有点的 min(nums0,nums1) 作为答案的贡献即可。

Code

#include <iostream>
#include <cstring>
using namespace std;

const int N = 100010, M = 400010;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int val[N]; // 判断无解时用的点值
bool st[N]; // dfs连通块,判断某个点是否遍历过
int color[N][31]; // color(i, j) 表示第i个点在j位上的值(0 -> 未涂色, 1 -> 1, 2 -> 0)

void add (int a, int b, int c)
{
    e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++ ;
}

// 判断是否无解
void dfs (int u, int v)
{
    val[u] = v; st[u] = true;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (st[j]) continue;
        if (val[j] == -1) dfs(j, w[i] ^ val[u]);
    }
    
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if ((val[u] ^ val[j]) != w[i]) // 矛盾
        {
            cout << -1 << endl;
            exit(0);
        }
    }
}

// 找出某一位0和1的数量
void dfs (int u, int & one, int & zero, int c, int bit)
{
    color[u][bit] = c; st[u] = true;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!color[j][bit])
        {
            if (w[i] >> bit & 1)
            {
                if (c == 1) ++ zero; else ++ one;
                dfs(j, one, zero, 3 - c, bit);
            }
            else
            {
                if (c == 1) ++ one; else ++ zero;
                dfs(j, one, zero, c, bit);
            }
        }
    }
}

int main ()
{
    cout.tie(0)->sync_with_stdio(0);
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 0, u, v, w; i < m && cin >> u >> v >> w;  i++ )
        add(u, v, w), add(v, u, w);
    memset(val, -1, sizeof val);
    for (int i = 1; i <= n; i ++ ) if (!st[i]) dfs(i, 1);
    long long res = 0;
    memset(st, 0, sizeof st);
    for (int i = 1; i <= n; i ++ )
        if (!st[i])
        {
            for (int b = 30; b >= 0; b -- )
            {
                int one = 1, zero = 0;
                dfs(i, one, zero, 1, b);
                res = res + min(one, zero) * (1ll << b);
            }
        }
    cout << res << endl;
    return 0;
}

F. Encoded Strings I

题意

给定一个长度为 n 的字符串 s 。对于每个前缀字符串 pres ,将其重新编码,规则为:

设某个字符 cpres最后一个位置kchr[k,len(pres)] 中不同字符的数量。那么把 c 映射为第 chr+1 个阿拉伯字母。

求出所有前缀字符串 pres 最大的映射字符串。

分析

cnt 存储每个字符最后一位后面的不同字符。

从前往后遍历前缀,当加入一个字符时,这个字符最后一个位置变为末尾,cnt 清空。对于其他在前面出现的字符,它的 cnt 数组加上这个字符即可。

Code

#include <iostream>
#include <string>
#include <set>
#include <map>
using namespace std;

int main ()
{
    int n; cin >> n;
    string s, ret; cin >> s;
    map<char, set<char> > cnt;
    set<char> appear;
    for (int i = 0; i < s.size(); i ++ )
    {
        string cur;
        cnt[s[i]].clear();
        appear.insert(s[i]);
        for (auto c : appear) if (s[i] != c) cnt[c].insert(s[i]);
        for (int j = 0; j <= i; j ++ )
            cur += char('a' + cnt[s[j]].size());
        ret = max(ret, cur);
    }
    cout << ret << endl;
    return 0;
}

J. Luggage Lock

题意

有四位的密码锁,每次可以选择把一段连续的区间向上或者向下转动一格,问把初始状态转成目标状态至少需要多少次旋转。

分析

对于某一位数字 ai ,如果要把他向上转动 k 次,我们同样可以让他向下转动 10k 次,每个数字都有两种状态(除了 k=0 ,也就是初始和目标相同,它可以向上转动 10 次,也可以向下转动 10 次,也可以选择不转动)。所以我们可以三进制枚举每一个旋转状态 b1b2b3b4

当我们得到一个旋转状态后呢?我们需要把这个状态变为全 0 ,由于每次可以选择一段连续区间 +1  or  1 ,可以想到 差分数组 ,我们把状态变为全 0 也就是意味着把差分数组变为全 0

在一个差分数组中,变为全 0 的最小代价为 max(posi,|neg|)posi 表示正数总和,neg 表示负数总和。

Code

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

int rot[5], back[5];

int main ()
{
    int T; cin >> T; while( T -- )
    {
        string a, b; cin >> a >> b;
        for (int i = 0; i < 4; i ++ )
            rot[i] = a[i] - b[i]; // 向上为正,向下为负
        // 枚举每个数字的转动,向上或者向下(0有三种,所以要三进制枚举)
        int m = 3 * 3 * 3 * 3, ret = 1e9;
        for (int i = 0; i < m; i ++ )
        {
            int state = i;
            for (int j = 0; j < 4; j ++ )
            {
                if (state % 3 == 1)
                {
                    // 为1,反方向旋转
                    if (rot[j] > 0) back[j] = rot[j] - 10;
                    else back[j] = 10 + rot[j];
                }
                else if (state % 3 == 2 && !rot[j])
                {
                    // 为2,特判rot为0,由于在模1的时候0为10,所以这里为-10
                    back[j] = -10;
                }
                else back[j] = rot[j];
                state /= 3;
            }
            // 把旋转数组变为全0,即差分数组变为0
            int posi = 0, neg = 0; // 差分数组变为0的次数:max(posi, neg)
            for (int j = 0; j < 4; j ++ )
            {
                int dif = (j ? back[j] - back[j-1] : back[j]);
                if (dif > 0) posi += dif;
                else neg += dif;
            }
            ret = min(ret, max(posi, abs(neg)));
        }
        cout << ret << endl;
    }
    return 0;
}

J. Perfect Matchings

题意

对于一个 2n 个顶点的完全图,删除给定的一颗生成树,求剩下图的完美匹配数量有多少。

完美匹配,指最大数量的边集合,集合内任意两条边都没有公共顶点。

分析

  1. 对于一个 2n 顶点的完全图,它的完美匹配数量有 i=1n2i1 个。

    n 个顶点放到左边去匹配右边 n 个,选择 n 个顶点 C2nn ,匹配为 n!

    然后对于每一个匹配边,它的一个结点在右边时贡献一次,左边时又贡献一次,所以要除以二。

    所以完美匹配数量为 C2nn×n!2 个。

    C2nn 中的偶数项除以二后与 n! 抵消,所以匹配数量为 1×3×5(2×n1)=i=1n2i1

  2. 容斥原理

    用总的图的所有匹配数量减去其中有一些匹配边在生成树上的数量,枚举有几条边在生成树,减去所有不合法情况就是合法的情况。

  3. 计数dp,枚举在生成树上的不合法情况

    f(i,j,0/1) 表示以 i 为子树,匹配数量为 ji 参与/不参与匹配时的方案数量。

    树上背包问题,设 ji 的儿子,转移方程为:

    f(i,x+y,0)+=f(i,x,0)(f(j,y,0)+f(j,y,1)),y>0f(i,x+y,1)+=f(i,x,1)(f(j,y,0)+f(j,y,1)),y>0f(i,x+y+1,1)+=f(i,x,0)f(j,y,0)

    可以枚举每个 i 子树的大小使复杂度达到 O(n2)

Code

#include <iostream>
#include <cstring>
using namespace std;

const int N = 4010, M = 8010, mod = 998244353;
int n, m;
int h[N], e[M], ne[M], idx;
int siz[N]; // 每个子树的结点个数
int f[N][N][2]; // f(i, j, k) 表示以i为根的树,取j个配对,i有无选中的方案数量
int p[N]; // p(i) 表示i个结点组成的完全图可配对的数量

void add (int a, int b)
{
    e[idx] = b; ne[idx] = h[a]; h[a] = idx ++ ;
}

void dfs (int u, int fa)
{
    f[u][0][0] = 1; siz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        dfs(j, u);
        for (int x = siz[u] / 2; x >= 0; x -- )
            for (int y = siz[j] / 2; y >= 0; y -- )
            {
                if (y > 0)
                {
                    f[u][x + y][0] = (f[u][x + y][0] + 1ll * f[u][x][0] * (f[j][y][0] + f[j][y][1]) % mod) % mod;
                    f[u][x + y][1] = (f[u][x + y][1] + 1ll * f[u][x][1] * (f[j][y][0] + f[j][y][1]) % mod) % mod;
                }
                f[u][x + y + 1][1] = (f[u][x + y + 1][1] + 1ll * f[u][x][0] * f[j][y][0] % mod) % mod;
            }
        siz[u] += siz[j];
    }
}

signed main ()
{
    int n; cin >> n;
    p[0] = 1; for (int i = 1; i <= n; i ++ ) p[i] = 1ll * p[i-1] * (2 * i - 1) % mod;
    memset(h, -1, sizeof h);
    for (int i = 1, u, v; i < 2 * n && cin >> u >> v; i ++ ) add(u, v), add(v, u);
    dfs(1, -1);
    int res = 0;
    for (int i = 0; i <= n; i ++ ) // 枚举生成树里的配对数量
    {
        if (i & 1) res = (1ll * res - 1ll * (f[1][i][0] + f[1][i][1]) * p[n - i] % mod) % mod;
        else res = (1ll * res + 1ll * (f[1][i][0] + f[1][i][1]) * p[n-i] % mod) % mod;
    }
    cout << (res % mod + mod) % mod << endl;
    return 0;
}

本文作者:Horb7

本文链接:https://www.cnblogs.com/Rainea/p/15585621.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Rainea  阅读(660)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.