2022 HDU多校3

Equipment Upgrade(概率 DP、分治 NTT)

Problem

一件装备初始等级是\(0\),现在需要把装备升到\(n\)级。在等级为\(i\)(\(0\le i\lt n\))的时候,可以花费\(c_i\)元,有\(p_i\)的概率升级到\(i+1\)级,而又\((1-p_i)\frac{w_j}{\sum_{k=1}^iw_k}\)的概率降到\(i-j\)(\(1\le j\le i\))级。问期望花费多少钱可以把装备升级到\(n\)

  • \(2\le n\le 10^5\)
  • \(p_0=1\),对于\(1\le i\le n-1\)\(1\le p_i,c_i\le 100\)

Solve

概率 DP 的套路很明显,假设\(dp_i\)表示从等级\(i\)升级到等级\(n\)需要的期望花费。令\(S_i=\sum_{k=1}^iw_k\),容易得到转移为

\[dp_i=p_i\times dp_{i+1}+\left( \sum_{j=1}^i(1-p_i)\frac{w_j}{S_i}dp_{i-j} \right)+c_i\\ =p_i\times dp_{i+1}+\left( \frac{(1-p_i)}{S_i}\sum_{j=1}^iw_jdp_{i-j} \right)+c_i \]

可以发现式子里面的和式变成了卷积的形式,但这样还不能求解,因为\(dp_{i}\)无法求到。加入我们把所有项都移动到左边,常数项不移动按照\(dp_i\)的下标从左到右排好,那么这样列出来可以发现这是一个矩阵并且类似下三角。

\[\left[ \begin{matrix} dp_0& -p_0dp_1 & 0 &\cdots &0&0&c_0\\ -\frac{(1-p_1)}{S_1}w_1dp_0 & dp_1 & -p_1dp_2 & \cdots &0&0&c_1\\ \vdots & \vdots &\vdots &\ddots & \vdots &\vdots &\vdots\\ -\frac{1-p_{n-1}}{S_{n-1}}w_{n-1}dp_0 & -\frac{1-p_{n-1}}{S_{n-1}}w_{n-2}dp_1 & -\frac{1-p_{n-1}}{S_{n-1}}w_{n-3}dp_2 & \cdots & dp_{n-1}& -p_{n-1}dp_n& c_{n-1} \end{matrix} \right] \tag{3} \]

如果去解这个方程组,时间复杂度是\(O(n^3)\)的,显然不科学,继续观察。可以发现,它们是线性相关的,如果用\(dp_0\)表示\(dp_1\),那么依次代入下去,\(dp_2\)也可以用\(dp_0\)表示,一直到\(dp_n\)都可以用\(dp_0\)表示。因此不妨设\(dp_i=x_idp_0+y_i\)表示\(dp_i\)可以被\(dp_0\)线性表出,那么\(dp_0=-\frac{y_n}{x_n}\)

根据上面的式子,我们先用所有\(0\le j\lt i\)\(dp_j\)来表示\(dp_i\),可以得到

\[dp_{i+1}=\frac{dp_i-c_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_jdp_{i-j}}{p_i} \]

把所有\(dp_{i}\)用线性递推式表示,得到

\[x_{i+1}dp_0+y_{i+1}=\frac{x_idp_0+y_i-c_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_j(x_{i-j}dp_0+y_{i-j})}{p_i}\\ x_{i+1}dp_0+y_{i+1}=\frac{x_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_jx_{i-j}}{p_i}dp_0+\frac{y_i-c_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_jy_{i-j}}{p_i} \]

对应系数相等,得到

\[x_{i+1}=\frac{x_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_jx_{i-j}}{p_i}\\ y_{i+1}=\frac{y_i-c_i-\frac{1-p_i}{S_i}\sum_{j=1}^iw_jy_{i-j}}{p_i} \]

Code

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;

const int N = 5e6+10;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;

const int mod = 998244353;

ll qpow(ll a, ll b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
namespace Poly
{
    #define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
    #define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
    #define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
    #define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了

    typedef vector<ll> poly;
    const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
    //一般模数的原根为 2 3 5 7 10 6
    const int inv_G = qpow(G, mod - 2);
    int RR[N], deer[2][21][N], inv[N];

    void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
        for(int p = 1; p <= t; ++ p) {
            int buf1 = qpow(G, (mod - 1) / (1 << p));
            int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
            deer[0][p][0] = deer[1][p][0] = 1;
            for(int i = 1; i < (1 << p); ++ i) {
                deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
                deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
            }
        }
        inv[1] = 1;
        for(int i = 2; i <= (1 << t); ++ i)
            inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }

    inline int NTT_init(int n) {//快速数论变换预处理
        int limit = 1, L = 0;
        while(limit <= n) limit <<= 1, L ++ ;
        for(int i = 0; i < limit; ++ i)
            RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
        return limit;
    }

    inline void NTT(poly &A, int type, int limit) {//快速数论变换
        A.resize(limit);
        for(int i = 0; i < limit; ++ i)
            if(i < RR[i])
                swap(A[i], A[RR[i]]);
        for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
            int len = mid >> 1;
            for(int pos = 0; pos < limit; pos += mid) {
                int *wn = deer[type][j];
                for(int i = pos; i < pos + len; ++ i, ++ wn) {
                    int tmp = 1ll * (*wn) * A[i + len] % mod;
                    A[i + len] = ck(A[i] - tmp + mod);
                    A[i] = ck(A[i] + tmp);
                }
            }
        }
        if(type == 0) {
            for(int i = 0; i < limit; ++ i)
                A[i] = 1ll * A[i] * inv[limit] % mod;
        }
    }

    inline poly poly_mul(poly A, poly B) {//多项式乘法
        int deg = A.size() + B.size() - 1;
        int limit = NTT_init(deg);
        poly C(limit);
        NTT(A, 1, limit);
        NTT(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            C[i] = 1ll * A[i] * B[i] % mod;
        NTT(C, 0, limit);
        C.resize(deg);
        return C;
    }
}
int main(){
    ios::sync_with_stdio(false);
    Poly::init(19);
    ll inv100=qpow(100,mod-2);
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        vector<ll>p(n),c(n),w(n),s(n),invp(n);
        for(int i=0;i<n;i++) cin>>p[i]>>c[i];
        for(int i=0;i<n;i++) p[i]=p[i]*inv100%mod;
        for(int i=0;i<n;i++) invp[i]=qpow(p[i],mod-2)%mod;
        for(int i=1;i<n;i++) cin>>w[i];
        for(int i=1;i<n;i++) s[i]=(s[i-1]+w[i])%mod;
        for(int i=1;i<n;i++) s[i]=qpow(s[i],mod-2);
        Poly::poly x(n+1),y(n+1);
        x[0]=1;
        auto CDQ=[&](auto self,int l,int r)->void{
            if(l==r){
                if(l==0) return;
                x[l]=(x[l-1]-(1-p[l-1]+mod)*s[l-1]%mod*x[l]%mod+mod)%mod*invp[l-1]%mod;
                y[l]=(y[l-1]-c[l-1]+mod-(1-p[l-1]+mod)*s[l-1]%mod*y[l]%mod+mod)%mod*invp[l-1]%mod;
                return;
            }
            int mid=(l+r)>>1;
            self(self,l,mid);
            Poly::poly P(r-l),Q(mid-l+1);
            for(int i=0;i<r-l;i++) P[i]=w[i];
            for(int i=l;i<=mid;i++) Q[i-l]=x[i];
            auto res1=Poly::poly_mul(P,Q);

            for(int i=mid+1;i<=r;i++) x[i]=(x[i]+res1[i-l-1])%mod;

            for(int i=0;i<r-l;i++) P[i]=w[i];
            for(int i=l;i<=mid;i++) Q[i-l]=y[i];
            auto res2=Poly::poly_mul(P,Q);
            for(int i=mid+1;i<=r;i++) y[i]=(y[i]+res2[i-l-1])%mod;

            self(self,mid+1,r);
        };
      CDQ(CDQ,0,n);
      cout<<(mod-y[n]*qpow(x[n],mod-2)%mod)%mod<<'\n';
    }
    return 0;
}

Boss Rush(二分、状态压缩)

Problem

勇士打Boss,Boss的血量为\(HP\),勇士有\(n\)个技能,每个技能有一个冷却时间\(t_i\)和持续时间\(len_i\),在持续时间内每秒会造成\(d_{ij}\)的伤害\((0\le j\lt len_i)\),在技能冷却时间内不能释放其它技能,但可以造成技能伤害,比如如果在时间\(x\)释放技能\(i\),那么在时间\([x,x+t_i]\)时间内不能使用其它技能,但技能效果是即可生效的,并且每个技能只能使用\(1\)次,问最少需要多少时间可以打败Boss,如果不能打败,就输出\(-1\)

\(1\le n\le 18\)\(1\le t_i,len_i\le 10^5\)\(1\le HP\le 10^{18}\)\(1\le d_{ij}\le 10^9\)

Solve

为了使时间最少,技能的使用肯定是无缝衔接的,即一个技能使用完等完它的冷却时间后立马使用另一个技能。注意到\(n\)很小,所以考虑使用状态压缩来表示使用过的技能集合,然后我们二分打败Boss的时间。用\(dp[s]\)表示使用技能集合为\(s\)时可以造成的最大伤害,这是显然的,因为肯定最大化技能组合伤害,如果一个存在一个集合的总冷却时间小于当前二分时间并且技能伤害大于\(HP\),说明这个二分时间充裕,我们可以减小它。考虑如何转移,假设我们当前的集合是\(s\),加入一个当前不在集合中的技能\(j\),如果\(s\)的总时间加上技能\(j\)的持续时间小于二分时间,那么就把这个技能持续时间可以造成的总伤害加入答案中,否则,就加入刚刚好不超过二分时间的那部分伤害。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
ll dmg[20][N];
ll t[20],len[20],sum[(1<<18)+1];
ll dp[(1<<18)+1];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--){
        int n;
        ll hp;
        cin>>n>>hp;
        ll l=0,r=0;
        for(int i=0;i<n;i++){
            cin>>t[i]>>len[i];
            r+=t[i]+len[i]-1;
            for(int j=0;j<len[i];j++){
                cin>>dmg[i][j];
                if(j) dmg[i][j]+=dmg[i][j-1];
            }
        }
        memset(sum,0,sizeof sum);
        for(int i=0;i<(1<<n);i++)
            for(int j=0;j<n;j++){
                if((i>>j)&1) sum[i]+=t[j];
            }
        auto check=[&](ll x)->bool{
            for(int i=0;i<(1<<n);i++)  dp[i]=-1;
            dp[0]=0;
            for(int i=0;i<(1<<n);i++){
                if(dp[i]<0) continue;
                if(dp[i]>=hp) return true;
                if(sum[i]>x) continue;
                for(int j=0;j<n;j++){
                    if(!((i>>j)&1)){
                        if(sum[i]+len[j]-1<=x)
                             dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]+dmg[j][len[j]-1]);
                        else dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]+dmg[j][x-sum[i]]);
                    }
                }
            }
            return false;
        };
        ll ans=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid)){
                ans=mid;
                r=mid-1;
            }else l=mid+1;
        }
        cout<<ans<<'\n';
    }
}

Dusk Moon(计算几何、凸包、最小圆覆盖)

Problem

平面上有\(n\)个点\((x_i,y_i)\),有\(q\)次操作,每次操作分两种

  • 将第\(j\)个点移动到\((x^{'},y_{'})\)
  • 查询区间\([l,r]\)构成的点集合的最小圆覆盖

保证点随机给出

\(1\le n,q\le 10^5\)

Solve

Code

Laser Alarm(计算几何、调整)

Problem

给定空间中的\(n\)条线段,求一个平面最多可能和几条线段接触,输出这个最大值,不要求求出平面。

\(1\le n\le 50\)

Solve

和之前做过的在二维平面上给定若干条线段,问是否存在一条直线使得这些线段在这条直线上的投影有交(POJ(3304)Segments)类似。可以发现,我们也可以通过调整,使得一个平面和\(3\)条线段的端点接触,而且\(n\)很小,直接枚举那三个端点然后暴力判断每一个直线和平面接触的情况即可。

  • 三维空间平面表示:一个点和一个法向量
  • 判断平面和线段相交:分别求平面上的点和线段的两个端点组成的向量和平面法向量的点积,答案可能一个\(\le 0\),一个\(\gt 0\)。因为这样说明存在等于\(0\)的点积,说明存在线段上的点和平面上已知的点组成的向量和平面法向量垂直,进而说明这个线段上的点在平面上

Code

#include <bits/stdc++.h>
using namespace std;
double eps=1e-8;
struct Point{
    double x,y,z;
    Point operator + (const Point &t)const{
        return {x+t.x,y+t.y,z+t.z};
    }
    Point operator - (const Point &t)const{
        return {x-t.x,y-t.y,z-t.z};
    }
    Point operator ^ (const Point &t)const{
        return {y*t.z-z*t.y,-(x*t.z-z*t.x),x*t.y-y*t.x};
    }
    double operator * (const Point &t)const{
        return x*t.x+y*t.y+z*t.z;
    }
    Point operator * (const double k)const{
        return {x*k,y*k,z*k};
    }
    double len(){
        return sqrt((*this)*(*this));
    }
}p[105];
struct Line{
    Point st,ed;
}seg[55];
typedef Point Vector;
struct Plane{
    Point a;
    Vector v;
};
bool seg_cross_plane(Line line,Plane plane){
    Vector v1=plane.a-line.st;
    Vector v2=plane.a-line.ed;
    double t1=v1*plane.v,t2=v2*plane.v;
    if(fabs(t1)<eps || fabs(t2)<eps)return true;
    if((t1>eps)^(t2>eps)) return true;
    return false;
}
int main(){
   ios::sync_with_stdio(false);
   cin.tie(nullptr);
   int T;
   cin>>T;
   while(T--){
     int n;
     cin>>n;
     for(int i=1;i<=n;i++) {
        cin>>seg[i].st.x>>seg[i].st.y>>seg[i].st.z;
        cin>>seg[i].ed.x>>seg[i].ed.y>>seg[i].ed.z;
        p[2*i-1]=seg[i].st,p[2*i]=seg[i].ed;
     }
     int ans=0;
     for(int i=1;i<2*n;i++)
        for(int j=1;j<i;j++){
            for(int k=1;k<j;k++){
               Vector v=(p[i]-p[j])^(p[i]-p[k]);
               if(v.len()<eps) continue;
               Plane plane={p[i],v};
               int res=0;
               for(int l=1;l<=n;l++){
                  if(seg_cross_plane(seg[l],plane)) res++;
               }
               ans=max(ans,res);
            }
            int res=0;
            Vector v=p[i]-p[j];
            //判断共线的情况
            for(int l=1;l<=n;l++){
                Vector p1=(p[i]-seg[l].st), p2=(p[i]-seg[l].ed);
                Vector t1=v^p1, t2=v^p2;
                if(t1.len()<eps || t2.len()<eps) res++;
            }
            ans=max(ans,res);
        }
        cout<<ans<<'\n';
   }
   return 0;
}

Package Delivery(贪心)

Problem

\(n\)个快递,每个快递有到货日期\(l_i\)了截止取货日期\(r_i\),每次最多拿\(k\)个,最少需要拿多少次

\(1\le n\le 10^5\)\(1\le l_i,r_i\le 10^9\)

Solve

一个贪心的想法是,肯定是尽可能堆积货物一次性拿。考虑如何贪心,所以快递可以看做有一个取货时间区间\([l,r]\),按照\(l\)从小到大排序。我们维护一个优先队列(右端点最小的放在队头),并且维护一个当前可接受的最小截止日期\(lim\)和当前拿走的快递数\(cnt\),对于优先队列队头的一个区间\([l,r]\),如果\(r\lt lim\),那么这个快递现在就必须拿走了,并且弹出队列,一直到不能弹为止。然后对于未处理的快递,如果\(l\lt lim\)并且\(r> lim\),那么这个快递就可以堆积一阵子以后拿,放入优先队列中,否则就要拿走。如果最后没有拿满\(k\)个,就把队列中截止日期最早的拿走。然后把\(lim\)拉高

Code

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node{
    int l,r;
    bool operator < (const node&t)const{
        return r>t.r;
    }
}p[N];
bool cmp(node a,node b){
    return a.l<b.l;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--){
        int n,k;
        cin>>n>>k;
        set<int>s;
        for(int i=1;i<=n;i++)
            cin>>p[i].l>>p[i].r,s.insert(p[i].r);
        sort(p+1,p+1+n,cmp);
        priority_queue<node>q;
        int cnt=0,ans=0,i=1;
        auto ptr=s.begin();
        while(ptr!=s.end()){
            int lim=*ptr;
            while(q.size() && q.top().r<=lim && cnt<k){
                q.pop();
                cnt++;
            }
            while(i<=n && p[i].l<lim &&cnt<k){
                if(p[i].r<=lim) cnt++;
                else q.push(p[i]);
                i++;
            }
            while(cnt && q.size() && cnt<k){
                cnt++;
                q.pop();
            }
            if(cnt) cnt=0,ans++;
            else ptr++;
        }
        cout<<ans<<'\n';
    }
}

Range Reachability Query(bitset、分块)

Problem

给定一个有向无环图,点的编号从\(1\)\(n\),边的编号从\(1\)\(m\)。有\(q\)个询问,每次给定一个点对\((u_i,v_i)\)和区间\([l_i,r_i]\),问是否可以从\(u_i\)出发,只经过在\([l_i,r_i]\)中的边到达\(v_i\)

\(2≤n≤50000, 1≤m≤100000, 1≤q≤50000\)

Solve

Code

Taix(二分、曼哈顿距离)

Problem

在二维平面上有\(n\)个点,每个点有一个坐标\((x_i,y_i)\)和权值\(w_i\)。现在有\(q\)个询问,每次给定一个坐标\((x,y)\),这个点到其它点的花费是\(\min(|x-x_i|+|y-y_i|,w_i)\),求最大的花费是多少。

\(1\le n,q\le 10^5\)\(1\le x_i,y_i,w_i\le 10^9\)

Solve

首先看到绝对值的第一想法就是拆,先不考虑\(w_i\)的话,那么就是求\(x-y+\max(-x_i+y_i)\)\(x+y+\max(-x_i-y_i)\)\(-x+y+\max(x_i-y_i)\)\(-x-y+\max(x_i+y_i)\)\(4\)中情况中最大的。也就是说一个询问给定\((x,y)\),考虑后面每一个\(\max\)是多少即可。

但现在要考虑\(w_i\),将所有点按照\(w_i\)从小到大排序,那么在\(w_i\)按照从小到大的前提下,记录每一个后缀的\(-x_i+y_i、-x_i-y_i、x_i-y_i、x_i+y_i\)的最大值。然后在给定\((x,y)\)之后,可以\(O(1)\)求出\((x,y)\)到后缀中所有点的最大值。

问题求的是最小中的最大,所以考虑二分。二分位置,假设当前二分的位置是\(mid\),并且假设\(mid\)位置之后的后缀最大值是\(j\),并且取到最大值位置是\(x\)。假如\(w[mid]>=t\),那么\(mid\)后面的只能取\(|x-x_i|+|y-y_i|\)的部分,而最终要取最大的,那么\(mid\)后面最优的就是这个后缀最大值,所以不用考虑这后面的位置了,看前面的位置,相当于\(r=mid-1\)。反之,假如$w[mid]<t \(,那么\)mid\(前面的只会取到比\)w[mid]\(更小的,所以不考虑,而\)mid\(后是存在\)w[j]\(比\)w[mid]\(大的,并且\)|x-x_j|+|y-y_j|\(也比\)w[mid]\(大,答案肯定在\)j\(处取会比在\)mid\(处取更优,所以此时\)l=mid+1$。

Code

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
const int inf=5e9;
struct node{
    int x,y,w;
    bool operator < (const node &t)const{
        return w<t.w;
    }
}p[N];
int mx[4][N];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--){
        int n,q;
        cin>>n>>q;
        for(int i=1;i<=n;i++)
            cin>>p[i].x>>p[i].y>>p[i].w;
        sort(p+1,p+1+n);
        for(int i=0;i<4;i++) mx[i][n+1]=-inf;
        for(int i=n;i>=1;i--){
            mx[0][i]=max(mx[0][i+1],-p[i].x+p[i].y);
            mx[1][i]=max(mx[1][i+1],-p[i].x-p[i].y);
            mx[2][i]=max(mx[2][i+1],p[i].x-p[i].y);
            mx[3][i]=max(mx[3][i+1],p[i].x+p[i].y);
        }
        while(q--){
            int x,y;
            cin>>x>>y;
            int l=1,r=n,ans=0,t;
            while(l<=r){
                int mid=(l+r)>>1;
                t=max(max(x-y+mx[0][mid],x+y+mx[1][mid]),max(-x+y+mx[2][mid],-x-y+mx[3][mid]));
                if(p[mid].w>=t){
                    ans=max(ans,t);
                    r=mid-1;
                }else{
                    ans=max(ans,p[mid].w);
                    l=mid+1;
                }
            }
            cout<<ans<<'\n';
        }
    }
}

Two Permutations(DP)

Problem

给定两个长度为\(n\)的排列\(P\)\(Q\),现在有一个空的序列\(R\),可以进行\(2n\)次操作使得\(R\)变成一个长度为\(2n\)的序列,每次可以从\(P或\)Q\(中选一个出来从它的最左边弹出一个元素放入\)R$的最右边。现在给定一个长度为\(2n\)的序列\(S\),问多少可能的操作使得最后\(R=S\)

\(1\le n\le 3\times 10^5\)

Solve

容易想到\(dp_{i,j}\)表示现在\(P\)中取到\(i\)\(Q\)中取到\(j\)的方案数。如果\(S_{i+j+1}=P_{i+1}\),那么\(dp_{i+1,j}+=dp_{i,j}\);如果\(S_{i+j+1}=Q_{j+1}\),那么\(dp_{i,j+1}+=dp_{i,j}\)。直观上时间复杂度是\(O(n^2)\)的,但注意到\(P\)\(Q\)都是排列,也就是说每个数在\(S\)中只会出现\(2\)次,因此我们可以单独记录\(P\)中每个数字是否被更新过,如果出现\(P_i=Q_j\)的情况并且\(P_i\)这个数被更新过了,那么后面的情况就是固定的了,直接返回更新的数,因为每个数在两个排列中都只出现一次,遇到相等的情况是唯一的。用记忆化搜索即可通过。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        vector<int>P(n+1),Q(n+1),S(2*n+1),cnt(n+1,0);
        for(int i=1;i<=n;i++) cin>>P[i];
        for(int i=1;i<=n;i++) cin>>Q[i];
        for(int i=1;i<=2*n;i++){
            cin>>S[i];
            cnt[S[i]]++;
        }
        bool ok=1;
        for(int i=1;i<=n;i++)
            if(cnt[i]!=2){
                ok=0;
                break;
            }
        if(!ok){
            cout<<0<<'\n';
            continue;
        }
        vector<ll>dp(n+1,-1);
        auto dfs=[&](auto self,int i,int j,int k)->ll{
            if(k==2*n+1) return 1;
            if(i<=n&&j<=n&&P[i]==Q[j]){
                if(P[i]==S[k]){
                    if(dp[P[i]]==-1)
                        return dp[P[i]]=(self(self,i+1,j,k+1)+self(self,i,j+1,k+1))%mod;
                    else return dp[P[i]];
                }else{
                    return 0;
                }
            }
            if(i<=n && P[i]==S[k])
                return self(self,i+1,j,k+1);
            if(j<=n && Q[j]==S[k])
                return self(self,i,j+1,k+1);
            return 0;
        };
        cout<<dfs(dfs,1,1,1)<<'\n';
    }
}
posted @ 2022-08-25 00:36  Arashimu  阅读(43)  评论(0编辑  收藏  举报