6438. 【GDOI2020模拟01.16】树上的鼠

题目

由于时间过于久远,而且题面本身也很清晰,所以就懒得另外叙述题目大意了(还有思考历程)。

正解

先考虑一条链的情况(长度为奇数,这里的长度是指点的数量):
如果根在中点,先手无论移到哪里,后手都可以移到它的对称点去。
此时先手必败;
如果根不在中点,先手只要一开始移到中点,先手就赢了。
若长度为偶数,就将中间的两个点都看成中点。
先手第一步先移到离根比较远的那个中点上,以后就用一样的策略,每次到达对方的对称点。所以偶数时先手必胜。
然后这就可以推广到一棵树的情况。
可以发现先手必败的情况当且仅当满足以下条件:
树的直径的长度为奇数,并且根是直径的中点。

于是就可以DP了。设\(f_{i,j}\)表示\(i\)为根的子树,最深点的深度为\(j\)的方案数。
发现直接跑这个东西会挂。
改一下定义,将“最深点深度为\(j\)”改成“最深点深度不超过\(j\)
考虑转移。直接转移还是会挂。
然后就有了这个套路做法:长链剖分,在转移的时候先继承重儿子的信息,再和轻儿子的信息合并。
信息合并的时候,共同有的长度(两块信息的最小长度)上的信息可以暴力做,至于剩下的信息,可以发现就是个区间乘的操作。
线段树?没必要,直接打标记就可以了(有点像差分)。
所以信息合并的时间复杂度是两块信息的最小长度。
合并一次相当于减少了一条重链,总的时间复杂度就是所有重链的长度加起来,也就是\(O(n)\)

至于统计答案,这是有点复杂的,不过可以推。
这里就不详细解释了。


代码

代码可能有点丑,因为信息很多都是用链表来存的。
常数也很大。

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#include <list>
#define N 1000010
#define ll long long
#define mo 998244353
ll qpow(ll x,int y){
    ll res=1;
    for (;y;y>>=1,x=x*x%mo)
        if (y&1)
            res=res*x%mo;
    return res;
}
int n;
struct EDGE{
    int to;
    EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
int q[N],fa[N],len[N],hs[N];
void getq(){
    int head=1,tail=1;
    q[1]=1;
    while (head<=tail){
        int x=q[head++];
        for (EDGE *ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa[x]){
                fa[ei->to]=x;
                q[++tail]=ei->to;
            }
    }
}
list<ll> _data[N*2],*f[N],*tag[N];
int cnt;
void pd(list<ll>::iterator pf,list<ll>::iterator pt,list<ll> *t){
//  assert(pt!=t->end());
    ll tmp=*pt;
    *pf=*pf*tmp%mo;
    *pt=1;
    ++pt;
    if (pt!=t->end())
        *pt=*pt*tmp%mo;
}
ll pro[N],tagp[N],sum[N],ans,all[N];
int main(){
//  freopen("in.txt","r",stdin);
//  freopen("out.txt","w",stdout);
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<n;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        e[ne]={v,last[u]};
        last[u]=e+ne++;
        e[ne]={u,last[v]};
        last[v]=e+ne++;
    }
    getq();
    for (int i=n;i>=1;--i){
        int x=q[i];
        len[x]=1;
        for (EDGE *ei=last[x];ei;ei=ei->las)
            if (len[ei->to]+1>len[x])
                len[x]=len[ei->to]+1,hs[x]=ei->to;
    }
    for (int i=n;i>=2;--i){
        int x=q[i];
        if (!hs[x]){
            f[x]=&_data[++cnt];
            tag[x]=&_data[++cnt];
            f[x]->push_back(1);
            tag[x]->push_back(1);
            continue;
        }
        f[x]=f[hs[x]];
        f[x]->push_front(1);
        tag[x]=tag[hs[x]];
        tag[x]->push_front(1);
        for (EDGE *ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa[x] && ei->to!=hs[x]){
                int y=ei->to;
                auto pfx=f[x]->begin(),pfy=f[y]->begin();
                auto ptx=tag[x]->begin(),pty=tag[y]->begin();
                ll sumx=0,sumy=1;
                pd(pfx,ptx,tag[x]);
                sumx+=*pfx;
                pfx++,ptx++;
                for (int k=1;k<=f[y]->size();++k,++pfx,++pfy,++ptx,++pty){
                    pd(pfx,ptx,tag[x]),pd(pfy,pty,tag[y]);
                    (sumx+=*pfx)%=mo,(sumy+=*pfy)%=mo;
                    *pfx=((sumx*(*pfy)+sumy*(*pfx)-(*pfx)*(*pfy))%mo+mo)%mo;
                }
                if (pfx!=f[x]->end())
                    (*ptx*=sumy)%=mo;
            }
    }
//  return 0;
    for (int i=1;i<=n;++i)
        pro[i]=1,tagp[i]=1;
    int maxd=0;
    for (EDGE *ei=last[1];ei;ei=ei->las){
        int y=ei->to;
        auto pfy=f[y]->begin(),pty=tag[y]->begin();
//      printf("%d ",y);
        for (int k=0;k<f[y]->size();++k,++pfy,++pty){
            pd(pfy,pty,tag[y]);
//          printf("%d ",*pfy);
        }
//      printf("\n");
        maxd=max(maxd,(int)f[y]->size());
        ll s=1;
        pfy=f[y]->begin();
        int k;
        for (k=1;pfy!=f[y]->end();++pfy,++k){
            s=(s+*pfy)%mo;
            pro[k]=pro[k]*s%mo;
            sum[k]=(sum[k]+*pfy*qpow((s-*pfy+mo)%mo,mo-2))%mo;
        }
        tagp[k]=tagp[k]*s%mo;
    }
    pro[0]=1;
    for (int i=1;i<=maxd;++i){
        pro[i]=pro[i]*tagp[i]%mo;
        tagp[i+1]=tagp[i+1]*tagp[i]%mo;
        tagp[i]=1;
        ans=(ans+pro[i]-pro[i-1]-sum[i]*pro[i-1]%mo+mo+mo)%mo;
    }
    ans+=1;
    for (int i=n;i>=1;--i){
        int x=q[i];
        all[x]=1;
        for (EDGE *ei=last[x];ei;ei=ei->las)
            if (ei->to!=fa[x])
                all[x]=all[x]*(all[ei->to]+1)%mo;
    }
    printf("%lld\n",(all[1]-ans+mo)%mo);
    return 0;
}

总结

做这种博弈题的时候,将当前局面转化成“位置一样,但选择变少”是一种比较妙的决策。
对于这种有关链的长度的信息的合并,可以考虑一下长链剖分。

猜你喜欢

转载自www.cnblogs.com/jz-597/p/12238814.html
今日推荐