首页 > 其他分享 >[COCI2009-2010#4] PALACINKE 题解

[COCI2009-2010#4] PALACINKE 题解

时间:2024-10-23 21:10:17浏览次数:5  
标签:return COCI2009 val int 题解 Int PALACINKE operator Mod

前言

题目链接:洛谷

题意简述

\(n\) 个点,\(m\) 条边。每条边上有商店,经过一条边花费 \(1\) 单位时间,如果在边上的商店购物,额外花费 \(1\) 单位时间。需要购买 \(4\) 种物品,每个商店售出 \(1 \sim 4\) 种物品不等。请问,在 \(T\) 个单位时间内,从 \(1\) 出发购物,得到这 \(4\) 种物品,并回到 \(1\) 的方案数,对 \(5557\) 取模。你在购完并回到 \(1\) 后可以立即停止,不能在其他点逗留。

\(n \leq 25\),\(m \leq 500\),\(T \leq 10^9\)。

题目分析

四种物品很少,想到使用状压 DP。记 \(f_{i, u, \mathcal{S}}\) 表示经过了 \(i\) 个单位时间,现在在 \(u\),目前购买了的物品集合为 \(\mathcal{S}\),的方案数。初始状态 \(f_{0, 1, \varnothing} = 1\),答案即为 \(\sum \limits _ {i = 0} ^ T f_{i, 1, \{1, 2, 3, 4\}}\)。

转移其实不难,对于 \((u, v, S')\) 这条边,枚举是否进行购买即可。

  1. 不购买

    \[f_{i + 1, v, S} \gets f_{i, u, S} \]

  2. 购买

    \[f_{i + 2, v, S \cup S'} \gets f_{i, u, S} \]

其中 \(a \gets b\) 表示将 \(b\) 累加到 \(a\) 上。

直接做是 \(\Theta(2^{|S|}T(n + m))\),\(T\) 很大,肯定超时。转移这么简单,考虑使用矩阵快速幂优化。

由于 \(f_{i}\) 需要用到 \(f_{i - 1}\) 和 \(f_{i - 2}\),并且需要额外记录 \(ans\),所以我们转移是从 \(\displaystyle \begin{bmatrix} f_{i - 2} & f_{i - 1} & ans \end{bmatrix}\) 转移到 \(\displaystyle \begin{bmatrix} f_{i - 1} & f_{i} & ans' \end{bmatrix}\)。

考虑构造转移矩阵,注意到,把状态 \(\lambda\) 的值乘上 \(k\) 累加到 \(\varphi\),相当于在转移矩阵的第 \(\lambda\) 行,第 \(\varphi\) 列累加一个 \(k\)。(采用转移矩阵放置在左边的形式。)当然,我们这里所有 \(k = 1\)。这一部分预处理是简单的。考虑什么时候累加答案。发现,如果 \(v = 1\),并且目标状态 \(S\) 或者 \(S \cup S'\) 是全集,不光要累加到 \(f\) 上,也要累加到 \(ans\) 上。\(f_{i - 1}\) 放到答案矩阵左侧这一步,可以在转移矩阵中套一个单位矩阵。

\[\begin{gathered} {\large \left [ \begin{array}{ccc:ccc:c} f_{i-2, 1, \varnothing} & \cdots & f_{i-2, n, \{1, 2, 3, 4\}} & f_{i-1, 1, \varnothing} & \cdots & f_{i-1, n, \{1, 2, 3, 4\}} & ans \\ \end{array} \right ]} \\ {\Huge \times} \\ {\large \left [ \begin{array}{ccc:ccc:c} 0 & \cdots & 0 & & & & \\ \vdots & \ddots & \vdots & & & & \\ 0 & \cdots & 0 & & & & \\ \hdashline 1 & \cdots & 0 & & & & \\ \vdots & \ddots & \vdots & & & & \\ 0 & \cdots & 1 & & & & \\ \hdashline 0 & \cdots & 0 & 0 & \cdots & 0 & 1 \\ \end{array} \right ]} \\ {\Huge \Downarrow} \\ {\large \left [ \begin{array}{ccc:ccc:c} f_{i-1, 1, \varnothing} & \cdots & f_{i-1, n, \{1, 2, 3, 4\}} & f_{i, 1, \varnothing} & \cdots & f_{i, n, \{1, 2, 3, 4\}} & ans' \\ \end{array} \right ]} \end{gathered} \]

构造转移矩阵的代码
// a => false: f[i-2], true: f[i-1]
inline int cal(bool a, int u, int st) {
    return (u - 1) * (1 << 4) + st + (a ? n * (1 << 4) : 0);
}

B = 2 * n * (1 << 4);  // ans 的位置
for (int u = 1; u <= n; ++u)
for (int st = 0; st < 1 << 4; ++st) {
    toadd(trans(cal(true, u, st), cal(false, u, st)), 1);  // 单位矩阵
    for (int o = xym.head[u]; o; o = xym[o].nxt) {
        int v = xym[o].to;
        toadd(trans(cal(true, u, st), cal(true, v, st)), 1);
        if (v == 1 && st == 0b1111)
            toadd(trans(cal(true, u, st), B), 1);
        toadd(trans(cal(false, u, st), cal(true, v, st | xym[o].len)), 1);
        if (v == 1 && (st | xym[o].len) == 0b1111)
            toadd(trans(cal(false, u, st), B), 1);
    }
}
toadd(trans(B, B), 1);  // 右下角 ans 累加

时间复杂度降到了:\(\Theta\Big((n 2^{|S|})^3 \log T\Big) = \Theta(n^316^{|S|}\log T)\)。还是会超时,但是能拿到 \(70\)pts。

考场上,我想到这里就不会做了。事实上,我们发现 \(16^{|S|}\) 显然是算法瓶颈所在,在矩阵乘法立方的加持下,不能记录 \(2^{|S|}\)。那怎么做呢?

我们可以在一开始钦定某些商品不能购买,得到在「钦定意义」下的「至多」购买 \(x\) 个商品的方案 \(f(x)\)。我们求的是「恰好」购买 \(x = 4\) 个商品的方案 \(g(x)\)。使用二项式反演即可,参见我的学习笔记,适用 \(\mathtt{Thm}\ 2.1\),这里给出公式:

\[f(x) = \sum \limits _ {i = 0} ^ x \dbinom{n - i}{n - x} g(i) \Longleftrightarrow g(x) = \sum _ {i = 0} ^ x (-1)^{x - i} \binom{n - i}{n - x} f(i) \]

至于「钦定」后怎么求出 \(f(x)\),同样使用矩阵快速幂,不过这次矩阵大小是 \(2n + 1\),总的时间复杂度为 \(\Theta(2^{|S|}n^3\log T)\)。

代码

$70$ 分代码
#include <cstdio>
#include <cstring>
#include <unordered_map>
#include <limits>
#include <iostream>
using namespace std;

const int N = 30, M = 520;

namespace Mod_Int_Class {
    template <typename T, typename _Tp>
    constexpr bool in_range(_Tp val) {
        return std::numeric_limits<T>::min() <= val && val <= std::numeric_limits<T>::max();
    }
    
    template <auto mod = 1000000007, typename T = int, typename S = long long>
    class Mod_Int {
        static_assert(in_range<T>(mod), "mod must in the range of type T.");
        static_assert(std::is_integral<T>::value, "type T must be an integer.");
        static_assert(std::is_integral<S>::value, "type S must be an integer.");
        public:
            constexpr Mod_Int() noexcept = default;
            template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
            constexpr Mod_Int(_Tp v) noexcept: val(0) {
                if (0 <= v && v < mod) val = v;
                else val = (v % mod + mod) % mod;
            }
            
            constexpr T const& raw() const {
                return this -> val;
            }
            
            static constexpr inline T add(T a, T b) {
                return a >= mod - b ? a + b - mod : a + b;
            }
            static constexpr inline T sub(T a, T b) {
                return a < b ? a - b + mod : a - b;
            }
            static constexpr inline T mul(T a, T b) {
                return static_cast<S>(a) * b % mod;
            }
            static constexpr inline T& toadd(T& a, T b) {
                return a = add(a, b);
            }
            static constexpr inline T& tosub(T& a, T b) {
                return a = sub(a, b);
            }
            static constexpr inline T& tomul(T& a, T b) {
                return a = mul(a, b);
            }
            
            template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
            static constexpr inline T pow(T a, _Tp p) {
                T res = 1;
                for (; p; p >>= 1, tomul(a, a))
                    if (p & 1) tomul(res, a);
                return res;
            }
            
            template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
            static constexpr inline bool is_prime(_Tp val) {
                if (val < 2) return false;
                for (_Tp i = 2; i * i <= val; ++i)
                    if (val % i == 0)
                        return false;
                return true;
            }
            
            static constexpr inline T inv(T a) requires (is_prime(mod)) {
                assert(a != 0);
                return pow(a, mod - 2);
            }
            
            constexpr Mod_Int& operator + () const {
                return *this;
            }
            constexpr Mod_Int operator - () const {
                return sub(0, val);
            }
            constexpr Mod_Int inv() const {
                return inv(val);
            }
            
            constexpr friend inline Mod_Int operator + (Mod_Int a, Mod_Int b) {
                return add(a.val, b.val);
            }
            constexpr friend inline Mod_Int operator - (Mod_Int a, Mod_Int b) {
                return sub(a.val, b.val);
            }
            constexpr friend inline Mod_Int operator * (Mod_Int a, Mod_Int b) {
                return mul(a.val, b.val);
            }
            constexpr friend inline Mod_Int operator / (Mod_Int a, Mod_Int b) {
                return mul(a.val, inv(b.val));
            }
            template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
            constexpr friend inline Mod_Int operator ^ (Mod_Int a, _Tp p) {
                return pow(a.val, p);
            }
            
            constexpr friend inline Mod_Int& operator += (Mod_Int& a, Mod_Int b) {
                return a = add(a.val, b.val);
            }
            constexpr friend inline Mod_Int& operator -= (Mod_Int& a, Mod_Int b) {
                return a = sub(a.val, b.val);
            }
            constexpr friend inline Mod_Int& operator *= (Mod_Int& a, Mod_Int b) {
                return a = mul(a.val, b.val);
            }
            constexpr friend inline Mod_Int& operator /= (Mod_Int& a, Mod_Int b) {
                return a = mul(a.val, inv(b.val));
            }
            template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>
            constexpr friend inline Mod_Int& operator ^= (Mod_Int& a, _Tp p) {
                return a = pow(a.val, p);
            }
            
            constexpr friend inline bool operator == (Mod_Int a, Mod_Int b) {
                return a.val == b.val;
            }
            constexpr friend inline bool operator != (Mod_Int a, Mod_Int b) {
                return a.val != b.val;
            }
			
			constexpr Mod_Int& operator ++ () {
				this -> val + 1 == mod ? this -> val = 0 : ++this -> val;
				return *this;
			}
			constexpr Mod_Int& operator -- () {
				this -> val == 0 ? this -> val = mod - 1 : --this -> val;
				return *this;
			}
			constexpr Mod_Int operator ++ (int) {
				Mod_Int res = *this;
				this -> val + 1 == mod ? this -> val = 0 : ++this -> val;
				return res;
			}
			constexpr Mod_Int operator -- (int) {
				Mod_Int res = *this;
				this -> val == 0 ? this -> val = mod - 1 : --this -> val;
				return res;
			}
			
			friend std::istream& operator >> (std::istream& is, Mod_Int<mod, T, S>& x) {
				T ipt;
				return is >> ipt, x = ipt, is;
			}
			friend std::ostream& operator << (std::ostream& os, Mod_Int<mod, T, S> x) {
				return os << x.val;
			}
        protected:
            T val;
    };
    using mint = Mod_Int<5557, short, int>;
    constexpr mint operator ""_m (unsigned long long x) {
        return mint(x);
    }
    constexpr mint operator ""_mod (unsigned long long x) {
        return mint(x);
    }
}

using namespace Mod_Int_Class;

int n, m, t, B;

struct Graph {
    struct node {
        int to, len, nxt;
    } edge[M];
    int tot = 1, head[N];
    void add(int u, int v, int w) {
        edge[++tot] = {v, w, head[u]};
        head[u] = tot;
    }
    inline node & operator [] (const int x) {
        return edge[x];
    }
} xym;

inline int cal(bool a, int u, int st) {
    return (u - 1) * (1 << 4) + st + (a ? n * (1 << 4) : 0);
}

using umap = unordered_map<short, mint>;

struct Matrix {
    umap val[801];
    umap & operator [] (int x) {
        return val[x];
    }
    mint & operator () (int a, int b) {
        // a 转移到 b
        return val[a][b];
    }
    inline Matrix friend operator * (Matrix &a, Matrix &b) {
        static Matrix res;
        for (register int i = 0; i <= B; ++i) res[i].clear();
        for (register int i = 0; i <= B; ++i)
        for (auto& [k, av]: a[i])
        for (auto& [j, bv]: b[k])
            res[i][j] += av * bv;
        return res;
    }
} trans, base;

inline int calc(char c) {
    switch (c) {
        case 'B': return 0;
        case 'J': return 1;
        case 'M': return 2;
        case 'P': return 3;
        default:  return -1;
    }
}

signed main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; ++i) {
        static char str[8];
        scanf("%d%d%s", &u, &v, str);
        int w = 0;
        for (int j = 0; str[j]; ++j) w |= 1 << calc(str[j]);
        xym.add(u, v, w);
    }
    B = 2 * n * (1 << 4);
    scanf("%d", &t);
    for (int u = 1; u <= n; ++u)
    for (int st = 0; st < 1 << 4; ++st) {
        ++trans(cal(true, u, st), cal(false, u, st));
        for (int o = xym.head[u]; o; o = xym[o].nxt) {
            int v = xym[o].to;
            ++trans(cal(true, u, st), cal(true, v, st));
            if (v == 1 && st == 0b1111)
                ++trans(cal(true, u, st), B);
            ++trans(cal(false, u, st), cal(true, v, st | xym[o].len));
            if (v == 1 && (st | xym[o].len) == 0b1111)
                ++trans(cal(false, u, st), B);
        }
    }
    ++trans(B, B);
    base[0][cal(true, 1, 0)] = 1;
    for (; t; trans = trans * trans, t >>= 1) {
        if (t & 1) base = base * trans;
    }
    printf("%hd", base[0][B].raw());
    return 0;
}
#include <cstdio>
#include <cstring>
#include <unordered_map>
#include <limits>
#include <iostream>
using namespace std;

const int N = 30, M = 520;

namespace Mod_Int_Class {
    // see 70 pts code
}

using namespace Mod_Int_Class;

int n, m, t, B;

struct Graph {
    struct node {
        int to, len, nxt;
    } edge[M];
    int tot = 1, head[N];
    void add(int u, int v, int w) {
        edge[++tot] = {v, w, head[u]};
        head[u] = tot;
    }
    inline node & operator [] (const int x) {
        return edge[x];
    }
} xym;

inline int cal(bool a, int u) {
    return (u - 1) + (a ? n : 0);
}

struct Matrix {
    mint val[51][51];
    mint * operator [] (const int x) {
        return val[x];
    }
    mint const * operator [] (const int x) const {
        return val[x];
    }
    mint & operator () (const int a, const int b) {
        // a 转移到 b
        return val[a][b];
    }
    Matrix() {
        memset(val, 0x00, sizeof (val));
    }
    inline Matrix friend operator * (const Matrix &a, const Matrix &b) {
        static Matrix res;
        memset(res.val, 0x00, sizeof (res.val));
        for (register int i = 0; i <= B; ++i)
        for (register int k = 0; k <= B; ++k)
        for (register int j = 0; j <= B; ++j)
            res[i][j] += a[i][k] * b[k][j];
        return res;
    }
} trans, base;

inline int calc(char c) {
    switch (c) {
        case 'B': return 0;
        case 'J': return 1;
        case 'M': return 2;
        case 'P': return 3;
        default:  return -1;
    }
}

inline bool subset(int a, int b) {
    return (a & b) == a;
}

inline mint solve(int S) {
    memset(trans.val, 0x00, sizeof (trans.val));
    memset(base.val, 0x00, sizeof (base.val));
    
    for (int u = 1; u <= n; ++u){
        ++trans(cal(true, u), cal(false, u));
        for (int o = xym.head[u]; o; o = xym[o].nxt) {
            int v = xym[o].to;
            ++trans(cal(true, u), cal(true, v));
            if (v == 1)
                ++trans(cal(true, u), B);
            if (subset(xym[o].len, S)) {
                ++trans(cal(false, u), cal(true, v));
                if (v == 1)
                    ++trans(cal(false, u), B);
            }
        }
    }
    ++trans(B, B);
    base[0][cal(true, 1)] = 1;
    for (int x = t; x; trans = trans * trans, x >>= 1) {
        if (x & 1) base = base * trans;
    }
    return base[0][B];
}

mint f[4], ans;

signed main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; ++i) {
        static char str[8];
        scanf("%d%d%s", &u, &v, str);
        int w = 0;
        for (int j = 0; str[j]; ++j) w |= 1 << calc(str[j]);
        xym.add(u, v, w);
    }
    B = 2 * n;
    scanf("%d", &t);
    for (int st = 0; st < 1 << 4; ++st)
        f[__builtin_popcount(st)] += solve(st);
    for (int i = 0; i <= 4; ++i) {
        // C(4 - i, 4 - 4) = 1
        if ((4 - i) & 1)
            ans -= f[i];
        else
            ans += f[i];
    }
    printf("%hu", ans.raw());
    return 0;
}

标签:return,COCI2009,val,int,题解,Int,PALACINKE,operator,Mod
From: https://www.cnblogs.com/XuYueming/p/18387610

相关文章

  • [算法题解] Codeforces round 979 --D. QED's Favorite Permutation
    题目链接:https://codeforces.com/contest/2030/problem/D题目:题解:FavotitePermutation即每个数满足i=p[i];此题中,p数组每次询问后保持不变,s根据询问而改变。因此每次需要进行交换的数的位置是相同的,只需要检验s变更后,操作能否满足交换需求与否。故创建需数组need,预处理nee......
  • 题解:CF1225E Rock Is Push
    很玄妙的一道dp题。HintAnalysis首先你要确保你会做当石头没有/固定的情况,这道题其实也是dp。考虑石头带来的影响,唯一的作用就是限制你的移动,比方说你下面有\(3\)块石头,由于只能向右或向下移动,你实际上往下只能走到当前列第\(n-3\)行。于是对于石头的处理,设\(rs[i][j......
  • 题解:P11215 【MX-J8-T3】水星湖
    依旧是模拟赛赛题。HintAnalysis首先你注意到两棵相邻的树是一定不会死的,所以可能会死的只有自己种下去的树,队列维护。接着考虑对于每个位置,\(\text{bfs}\)维护一个最小的长出树的时间\(vis[i][j]\),最后暴力统计答案即可。具体细节看注释。Code#include<bits/stdc++.h>......
  • CRC32爆破脚本 + [MoeCTF 2022]cccrrc 题解
    CRC32爆破原理介绍:CRC(循环冗余校验)是一种用于检测数据传输错误的技术。CRC算法生成一个校验值(校验和),这个值可以附加到数据后面,在数据接收方重新计算校验值并与附加的校验值进行比较,以此来确定数据是否在传输过程中发生了错误CRC32是一种常用的CRC算法,它的校验值长度固定为3......
  • P7910 [CSP-J 2021] 插入排序 题解
    正解首先要注意$2$点:修改数组元素的值会影响接下来的操作.对数组进行排序不会影响接下来的操作.思路直接扫一遍数组.假设排序后$a_x$会在第$p$位上.将$p$初始化为$n$.然后就开始找$x$前后有多少个小于$a_x$的值就行了.时间复杂度:$\Theta(nq)$.注意......
  • P7911 [CSP-J 2021] 网络连接 题解
    模拟代码#include<iostream>#include<cstdio>#include<cstring>usingnamespacestd;intn,p=1,ans[1003];//没事干的ans数组structnode{ stringop,ad;}a[1003];intws(intn){//位数 intsum=0; if(n==0)return1; while(n){ n......
  • P8816 [CSP-J 2022] 上升点列 题解
    最长上升子序列根据题目中,每个坐标的横纵坐标均单调递增,所以明显可以使用最长上升子序列.定义状态$f_{i,p}$,表示正在节点$i$时,还剩下$p$次插入机会,所能达到的最大长度.定义变量$dis=|x_i-x_j|+|y_i-y_j|-1.$,表示$i$到$j$节点至少要插$dis$个节点.为什么要$-1$......
  • ARC165F题解
    前言\(2024.10.19\)日校测\(T4\),思维太庙,被薄纱了,遂哭弱,写题解以记之。简要题意给你一个长度为\(2n\)的序列\(A,\foralla_i\in[1,n]\),其中\(1\)到\(n\)每个数都出现了两次,现在需要把相同的两个数排到一起,每次操作只能交换相邻两个数,在保证操作次数最小的情况下求出现......
  • P8814 [CSP-J 2022] 解密 题解
    解方程$题目中说,n=pq,ed=(p-1)(q-1)+1,m=n-ed+2.$$把ed的式子展开,得到:$$ed=p(q-1)-(q-1)+1$$ed=pq-p-q+2$$再把展开后的式子带入m中,得:$$m=n-(pq-p-q+2)+2.$$m=n-pq+p+q-2+2$$\becausen=pq$$\thereforem=pq-pq+p+q-2+2$$m=p+q.$$如果想要求出p和q的值,那么可以再......
  • P8815 [CSP-J 2022] 逻辑表达式 题解
    短路我们可以使用一个变量来记录当前有没有短路.设变量短路为$dl$.当$dl$为$0$时,说明当前值为$0$,且运算符为&.当$dl$为$1$时,说明当前值为$1$,且运算符为|.代码重点讲完了,细节可以看代码以及注释.#include<iostream>#include<cstdio>#include<cstring>using......