• 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("
    ");
    		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
    ",(all[1]-ans+mo)%mo);
    	return 0;
    }
    

    总结

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

  • 相关阅读:
    volatile关键字解析(转载)
    php
    FFT快速傅立叶变换
    高次不定方程BSGS算法
    BSGS-BabyStepGiantStep算法+拓展
    Java-数组-面向对象
    Java基础-方法(2)和数组
    Java基础-循环(2)和方法
    Java基础-循环结构
    Java基础-运算符
  • 原文地址:https://www.cnblogs.com/jz-597/p/12238814.html
Copyright © 2020-2023  润新知