题解 CF1314B Double Elimination

感觉,是我做过最神的DP题之一了。

观察题目给出的比赛结构图。可以发现,除了最开始的那一轮胜者组比赛和最后一场决赛外,剩余的比赛构成了两棵相同的树形结构(而且是满二叉树)。其中,胜者组的一个节点代表一场比赛,败者组的一个节点代表两场比赛

让参赛队伍标号为\([0,2^n-1]\)。类似于建线段树的方法,我们从\([0,2^n-1]\)开始递归,每次把序列分成两半,除去长度为\(1\)的区间,共能得到\(2^{n}-1\)个区间。其中,每个区间形如\([a2^b,(a+1)2^b-1]\)。可以发现,上一段所说的“树形结构”中,树的节点和我们分出的这些区间一一对应。

也就是说,每个节点所代表的的比赛的两支参赛队伍,均来自这个节点所对应的区间。且对于胜者组败者组的第一场比赛,比赛的两支参赛队伍,一定是一支来自区间的左半边,另一支来自区间的右半边。而对于败者组的第二场比赛,一定是该节点中胜者组的输者败者组第一场的赢者去比。这三场比赛全部结束后,这个节点的胜者组、败者组会分别向该节点的父亲提供一支队伍,也就是该节点上两个组别的赢家。

而对于父亲节点来说,我们关心的,就是这“两个赢家”是否是我们喜欢的队伍。

于是想到一个DP。设\(dp[l\dots r][up][lo]\)表示标号在\([l,r]\)这个区间内队伍之间,也就是在树上\([l,r]\)这个节点的子树内,最多有多少场我们喜欢的队伍参加的比赛。\(up\in\{0,1\}\)表示这个节点的胜者组的赢家,是否是我们喜欢的队伍;\(lo\in\{0,1\}\)表示这个节点的败者组的赢家,是否是我们喜欢的队伍。

转移时,枚举左右区间分别的\(up,lo\),再枚举这个节点上三场比赛的结果,复杂度\(O(2^7)\)

总时间复杂度\(O(2^n2^7)\)

参考代码:

//problem:CF1314B
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
    if(S==T){
        T=(S=buf)+fread(buf,1,MAXN,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
    #define getchar Fread::getchar
#endif
inline int read(){
    int f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline ll readll(){
    ll f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=1<<20;
int n,m,dp[MAXN*4+5][2][2];
bool fan[MAXN];
#define forbit(i) for(int i=0;i<=1;++i)
inline void ckmax(int &x,int y){x=x>y?x:y;}
void solve(int id,int l,int r){
    if(l+1==r){
        //区间长度为2,递归边界
        if(!fan[l] && !fan[r])dp[id][0][0]=0;
        else if(!fan[l] || !fan[r])dp[id][1][0]=dp[id][0][1]=1;
        else dp[id][1][1]=1;
        return;
    }
    int mid=(l+r)>>1;
    solve(id<<1,l,mid);
    solve(id<<1|1,mid+1,r);
    forbit(l_up)forbit(l_lo)forbit(r_up)forbit(r_lo){
        if(dp[id<<1][l_up][l_lo]>=0&&dp[id<<1|1][r_up][r_lo]>=0){
            int res=dp[id<<1][l_up][l_lo]+dp[id<<1|1][r_up][r_lo]+(l_up||r_up)+(l_lo||r_lo);
            forbit(x)forbit(y)forbit(z){
                //枚举当前节点上的三场比赛的结果
                //x,y: 1表示左边赢,0表示右边赢
                //z:   1表示上边赢,0表示下边赢
                ckmax(dp[id][x?l_up:r_up][z?(x?r_up:l_up):(y?l_lo:r_lo)],res+((x?r_up:l_up)||(y?l_lo:r_lo)));
            }
        }
    }//O(2^7)
}
int main() {
    n=read();m=read();
    for(int i=1,x;i<=m;++i)x=read()-1,fan[x]=1;
    memset(dp,0xcf,sizeof(dp));
    solve(1,0,(1<<n)-1);
    int ans=0;
    forbit(i)forbit(j)ckmax(ans,dp[1][i][j]+(i||j));
    cout<<ans<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/dysyn1314/p/12505326.html