ZJOI2019 Day1 题解
想要继续向前,就从克服内心的恐惧开始。
麻将
题意
在麻将中,我们称点数连续的三张牌或三张点数一样的成为面子,称两张点数一样的牌为对子。一副十四张麻将牌的胡牌条件是可以分成四个面子和一个对子或者分成七个互不相同的对子。现在规定麻将牌的点数为\(1\sim n\),每种点数都有四张牌。现在给出你初始的十三张手牌,假设剩余的牌以随机的顺序发到你的手上,求期望发多少张牌以后你的手牌中存在一组牌可以胡牌。注意点数相同的牌两两之间也视为不同的。\(5\leq n\leq 100\)。答案对\(998244353\)取模。
题解
首先对题目要求的期望进行转化。假设\(p_i\)表示发了\(i\)张牌以后依然没有能够胡的一组牌的概率,那么答案等价于\(\sum_{i=0}^{4n-13}p_i\),这是通过将贡献分步计算得出的。
那么考虑将概率转化为更好算的方案数,最后只要令\(p_i\)除以\({4n-13\choose i}i!\),即放牌的方案数即可。
那么如何在给定一副牌的情况下,判段它是否存在一个子集是否可以胡牌呢?
我们可以用一个简单的\(dp\)来表示这个过程。对于七个对子的情况,只需记\(cnt_i\)表示点数\(\leq i\)的牌中牌数\(\geq 2\)的点数数量即可。对于四个面子加一个对子的情况,我们记\(f_{i,0/1,j,k}\)表示只考虑点数\(\leq i\)的牌,在是否加了对子,准备组成\(j\)副形如\(i-1,i,i+1\)的面子和\(k\)副形如\(i,i+1,i+2\)的面子的情况下,剩下的牌最多能够组成多少面子。(如果当前已经没有足够的牌满足这个要求,那么设为\(-1\)即可)。
考虑组成了\(3\)副点数连续的面子的情况,这可以拆成三副点数相同的面子,因此上面的\(f\)数组中的\(j,k\)的上界设为\(2\)就够了。又可以发现因为只需要\(4\)个面子,因此所有\(f_{i,0/1,j,k}\)的值都可以对\(4\)取\(\min\),同理\(cnt_i\)可以对\(7\)取\(\min\)。同时可以发现判断一个状态是否可以胡牌也是很简单的,只需要看是否有\(f_{i,0/1,j,k}=4\)或者\(cnt_i=7\)即可。
考虑这个状态的转移,可以枚举点数为\(i+1\)的牌数,设为\(c\),则\(f_{i+1,0,j,k}\)可以通过合法的\(f_{i,0,l,j}+l+\lfloor\frac{c-l-j-k}{3}\rfloor\)转移过来,\(f_{i+1,1,j,k}\)可以通过合法的\(f_{i,1,l,j}+l+\lfloor\frac{c-l-j-k}{3}\rfloor\)转移过来,如果将点数为\(i+1\)的取出两张做对子,\(f_{i+1,1,j,k}\)也可以通过合法的\(f_{i,0,l,j}+l+\lfloor\frac{c-2-l-j-k}{3}\rfloor\)转移过来。\(cnt\)的转移则更简单,只要令\(cnt_{i+1}=cnt_i+[c\geq 2]\)就可以了。
我们可以发现,状态的转移和\(i\)没有任何关系,只要知道了\(f_i\)的共\(2\times 3\times 3=18\)个数、\(cnt_i\)以及下一种点数的牌数,我们就可以得到\(f_{i+1}\)以及\(cnt_{i+1}\)。于是我们不妨将某种确定的\(f_i\)和\(cnt_i\)打包为一个结构体\(cell\)。在发现\(cell\)的状态十分简洁,并且每个数的取值都是有上下界的以后,我们不禁想到:合法的\(cell\)的数量会不会非常有限呢?为了验证这一点,我们可以从空状态开始,每次枚举下一种点数的牌数,就可以得到它的所有后继状态。我们可以进行一次\(bfs\)(可以用\(set\)判重)以找出所有空状态可以到达的状态。
最终我们可以发现,合法的状态一共有\(3956\)个。你可以用这个数字来检验程序的正确性。
既然状态数这么少,那么我们不妨预处理好所有的状态对应的转移。为了减小常数,我们不妨对每个不同的状态标一下号。于是我们就可以预处理数组\(trans\),其中\(trans_{x,i}\)表示在标号为\(x\)的状态下,如果下一种点数的牌数为\(i\),将会转移到标号为多少的状态。同时预处理数组\(book\),其中\(book_x\)表示状态\(x\)是否可以胡牌。
最终我们就可以计算数组\(p\)了,这可以通过一个“更大”的\(dp\)来实现。我们设\(g_{i,j,k}\)表示只考虑点数\(\leq i\)的牌,当前的状态标号为\(j\),点数\(\leq i\)的牌中除了初始给定的牌以外还选了\(k\)张牌的方案数。不难发现\(p_k=\sum_{book_j=0}g_{n,j,k}\)。
我们设\(cnt_i\)(注意这和之前的\(cnt\)完全没关系)表示初始时刻你的手牌中点数为\(i\)的牌的数量,来考虑大\(dp\)的转移。可以发现我们有:
\]
这样的关系,左边的系数的组合意义为,确定哪些牌作为拿到的牌,并决定这些牌的相对顺序,最后用隔板法插入到已有的牌中。
最终我们就得到了答案。复杂度是\(O(3956*n^2)\),可以通过本题。实现的时候为了节省空间大\(dp\)可以使用滚动数组。
代码:
/*
There is no end though there is a start in space. ---Infinity.
It has own power, it ruins, and it goes though there is a start also in the star. ---Finite.
Only the person who was wisdom can read the most foolish one from the history.
The fish that lives in the sea doesn't know the world in the land.
It also ruins and goes if they have wisdom.
It is funnier that man exceeds the speed of light than fish start living in the land.
It can be said that this is an final ultimatum from the god to the people who can fight.
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
using std::max;
using std::min;
using std::map;
using std::queue;
const int mod=998244353;
inline int add(int a,int b)
{
return (a+=b)>=mod?a-mod:a;
}
inline int sub(int a,int b)
{
return (a-=b)<0?a+mod:a;
}
inline int mul(int a,int b)
{
return (long long)a*b%mod;
}
inline int qpow(int a,int b)
{
int res=1;
for(;b;a=mul(a,a),b>>=1)
if(b&1)
res=mul(res,a);
return res;
}
struct epc
{
int a[2][3][3];
int cnt;
epc()
{
memset(a,-1,sizeof(a));
a[0][0][0]=0;
cnt=0;
return;
}
inline bool operator<(const epc &th) const
{
register int i,j,k;
for(i=0;i<2;i++)
for(j=0;j<3;j++)
for(k=0;k<3;k++)
if(a[i][j][k]!=th.a[i][j][k])
return a[i][j][k]<th.a[i][j][k];
return cnt<th.cnt;
}
inline epc trans(int c)
{
register int i,j,k;
epc res;
memset(res.a,-1,sizeof(res.a));
res.cnt=cnt+(c>=2);
res.cnt=min(res.cnt,7);
for(i=0;i<3;i++)
for(j=0;j<3;j++)
for(k=0;k<3;k++)
if(k+i+j<=c)
{
if(a[0][k][i]!=-1)
res.a[0][i][j]=max(res.a[0][i][j],a[0][k][i]+k+(c-(k+i+j))/3);
if(a[1][k][i]!=-1)
res.a[1][i][j]=max(res.a[1][i][j],a[1][k][i]+k+(c-(k+i+j))/3);
}
if(c>=2)
for(i=0;i<3;i++)
for(j=0;j<3;j++)
for(k=0;k<3;k++)
if(k+i+j<=c-2&&a[0][k][i]!=-1)
res.a[1][i][j]=max(res.a[1][i][j],a[0][k][i]+k+(c-2-(k+i+j))/3);
for(i=0;i<2;i++)
for(j=0;j<3;j++)
for(k=0;k<3;k++)
res.a[i][j][k]=min(res.a[i][j][k],4);
return res;
}
inline bool win()
{
bool f=0;
register int i,j;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
f|=a[1][i][j]==4;
f|=cnt==7;
return f;
}
};
const int M=4005;
map<epc,int> id;
int tot;
int trans[M][5];
bool book[M];
queue<epc> Q;
inline void bfs()
{
epc x,y;
register int i;
id[epc()]=++tot;book[tot]=epc().win();Q.push(epc());
while(!Q.empty())
{
x=Q.front();Q.pop();
for(i=0;i<5;i++)
{
y=x.trans(i);
if(id.find(y)==id.end())
id[y]=++tot,book[tot]=y.win(),Q.push(y);
trans[id[x]][i]=id[y];
}
}
return;
}
const int N=105;
int n,tans;
int cnt[N];
int dp[2][M][N*4];
int fact[N*4],ifact[N*4];
int ans[N*4];
inline int choose(int n,int m)
{
return mul(fact[n],mul(ifact[m],ifact[n-m]));
}
signed main()
{
bfs();
int x;
register int i,j,k,c;
scanf("%d",&n);
for(i=0;i<13;i++)
scanf("%d%*d",&x),cnt[x]++;
fact[0]=1;
for(i=1;i<=4*n;i++)
fact[i]=mul(fact[i-1],i);
ifact[4*n]=qpow(fact[4*n],mod-2);
for(i=4*n-1;i>=0;i--)
ifact[i]=mul(ifact[i+1],i+1);
dp[0][1][0]=1;
for(i=0;i<n;i++)
{
for(j=1;j<=tot;j++)
for(k=0;k<=4*(i+1);k++)
dp[(i+1)&1][j][k]=0;
for(j=1;j<=tot;j++)
for(k=0;k<=4*i;k++)
if(dp[i&1][j][k])
for(c=0;c<=4-cnt[i+1];c++)
dp[(i+1)&1][trans[j][cnt[i+1]+c]][k+c]=add(dp[(i+1)&1][trans[j][cnt[i+1]+c]][k+c],mul(dp[i&1][j][k],mul(mul(choose(4-cnt[i+1],c),fact[c]),choose(k+c,c))));
}
for(j=1;j<=tot;j++)
if(!book[j])
for(k=0;k<=4*n-13;k++)
ans[k]=add(ans[k],dp[n&1][j][k]);
for(k=0;k<=4*n-13;k++)
ans[k]=mul(ans[k],qpow(mul(choose(4*n-13,k),fact[k]),mod-2));
for(k=0;k<=4*n-13;k++)
tans=add(tans,ans[k]);
printf("%d\n",tans);
return 0;
}
线段树
题意
你有一棵按照常规方法建立的\([1,n]\)的线段树,每个节点上有一个\(0/1\)变量\(tag\)。每次可以对区间\([l,r]\)进行修改,修改的方法为:设定位到的节点集合为\(S\),则将\(S\)的所有祖先的\(tag\)按深度顺序下传。下传规则为:如果\(tag\)为\(1\),则将儿子的\(tag\)设为\(1\),并将自己的\(tag\)设为\(0\)。最后将所有\(S\)中点的\(tag\)设为\(1\)。现在\(m\)个操作,分为两种:
\(1\)、将当前的每棵线段树复制一份,并对所有复制品执行对给定区间\([l,r]\)的修改。
\(2\)、询问所有线段树\(tag\)为\(1\)的节点数目。
\(n,m\leq 10^5\)。答案对\(998244353\)取模。
题解
不妨将操作一看作是每次有\(\frac{1}{2}\)的概率对这棵线段树进行操作。这对我们接下来的考虑会方便一些。
于是我们对于线段树的每个节点,维护两个值\(p\),\(q\),表示这个节点\(tag\)为\(1\)的概率以及这个点到根的路径上有至少一个点\(tag\)为\(1\)的概率。那么我们只需要维护变量\(sum\)表示所有节点的\(p\)的和即可。假设已经做了\(cnt\)次\(1\)操作,那么询问的答案就是\(sum*2^{cnt}\)。
考虑每一次修改对每个节点的影响。我们可以将节点分为\(5\)类:
\(1\)、是\(S\)的祖先。
\(2\)、是\(S\)中的节点。
\(3、\)不是\(S\)中的节点,但它的父亲是\(S\)的祖先。
\(4\)、\(2\)类点的后代。
\(5\)、\(3\)类点的后代。
我们分别考虑影响:
\(1\)、比较显然,由于标记一定都被下传了,因此\(p\to \frac{p}{2}\),\(q\to\frac{q}{2}\)即可。
\(2\)、也比较简单,由于一定被打了标记,因此\(p\to\frac{p+1}{2}\),\(q\to\frac{q+1}{2}\)即可。
\(3\)、考虑在操作后,这类点会有标记当且仅当其祖先或者自己有标记。操作后这个点以及它的祖先至少有一个有标记的概率是不变的,因此\(p\to\frac{p+q}{2}\),\(q\to q\)。
\(4\)、这个操作对于\(p\)是没有影响的,但是由于它的祖先一定被打了标记,因此\(p\to p\),\(q\to \frac{q+1}{2}\)。
\(5\)、嗯,似乎没有任何影响呢。\(p\to p\),\(q\to q\)。
可以发现,前\(3\)类点的数目都不超过\(O(\log n)\)个,因此可以暴力修改,记得需要维护\(sum\)。唯一比较麻烦的是\(4\)类点。但由于修改比较单一,可以用懒标记实现。只需对每个点维护懒标记\(k\)、\(b\),表示其所有后代的\(q\)实际上都为\(kq+b\)即可。并且这类操作对\(sum\)的值也没有影响。
于是直接实现这些操作就可以了。复杂度\(O(n\log n)\)。
代码:
/*
There is no end though there is a start in space. ---Infinity.
It has own power, it ruins, and it goes though there is a start also in the star. ---Finite.
Only the person who was wisdom can read the most foolish one from the history.
The fish that lives in the sea doesn't know the world in the land.
It also ruins and goes if they have wisdom.
It is funnier that man exceeds the speed of light than fish start living in the land.
It can be said that this is an final ultimatum from the god to the people who can fight.
*/
#include<cstdio>
const int mod=998244353,inv2=(mod+1)/2;
inline int add(int a,int b)
{
return (a+=b)>=mod?a-mod:a;
}
inline int sub(int a,int b)
{
return (a-=b)<0?a+mod:a;
}
inline int mul(int a,int b)
{
return (long long)a*b%mod;
}
const int N=1e5+5;
int sum;
int p[N<<2],q[N<<2],k[N<<2],b[N<<2];
inline void push_down(int x)
{
q[x<<1]=add(mul(k[x],q[x<<1]),b[x]);
b[x<<1]=add(mul(k[x],b[x<<1]),b[x]);
k[x<<1]=mul(k[x],k[x<<1]);
q[x<<1|1]=add(mul(k[x],q[x<<1|1]),b[x]);
b[x<<1|1]=add(mul(k[x],b[x<<1|1]),b[x]);
k[x<<1|1]=mul(k[x],k[x<<1|1]);
k[x]=1;b[x]=0;
return;
}
void modify(int x,int lp,int rp,int l,int r)
{
if(lp==l&&rp==r)
{
sum=sub(sum,p[x]);
p[x]=add(mul(inv2,p[x]),inv2);
q[x]=add(mul(inv2,q[x]),inv2);
b[x]=add(mul(inv2,b[x]),inv2);
k[x]=mul(inv2,k[x]);
sum=add(sum,p[x]);
return;
}
push_down(x);
int mid=(lp+rp)>>1;
sum=sub(sum,p[x]);
p[x]=mul(inv2,p[x]);
q[x]=mul(inv2,q[x]);
sum=add(sum,p[x]);
if(r<=mid)
{
modify(x<<1,lp,mid,l,r);
sum=sub(sum,p[x<<1|1]);
p[x<<1|1]=mul(inv2,add(p[x<<1|1],q[x<<1|1]));
sum=add(sum,p[x<<1|1]);
}
else if(l>=mid+1)
{
sum=sub(sum,p[x<<1]);
p[x<<1]=mul(inv2,add(p[x<<1],q[x<<1]));
sum=add(sum,p[x<<1]);
modify(x<<1|1,mid+1,rp,l,r);
}
else
modify(x<<1,lp,mid,l,mid),modify(x<<1|1,mid+1,rp,mid+1,r);
return;
}
void build(int x,int lp,int rp)
{
k[x]=1;
if(lp==rp)
return;
int mid=(lp+rp)>>1;
build(x<<1,lp,mid);
build(x<<1|1,mid+1,rp);
return;
}
int n,m;
signed main()
{
int opt,l,r,w=1;
scanf("%d%d",&n,&m);
build(1,1,n);
while(m--)
if(scanf("%d",&opt),opt==1)
scanf("%d%d",&l,&r),modify(1,1,n,l,r),w=mul(w,2);
else
printf("%d\n",mul(w,sum));
return 0;
}
Minimax搜索
题意
有一颗\(n\)个点的数,标号为\(1\sim n\),根节点为\(1\),深度为\(1\)。每个点有一个权值。叶子的权值是它的标号,否则,假如其深度为奇数,那么其权值为所有儿子权值的\(\max\),否则为所有儿子权值的\(\min\)。一个叶子节点的集合的权值定义为:对于一个\(w\),\(f_w\)表示这个集合内的节点的权值可以修改为任意和原来权值的差的绝对值不超过\(w\)的整数,是否可以改变根节点的权值。那么这个叶子节点集合的权值为最小的使得\(f_w=1\)的\(w\)。特别的,假如不存在这样的\(w\),那么这个集合的权值为\(n\)。现在给定\(L\),\(R\),问对于所有整数\(i\in[L,R]\),权值为\(i\)的非空叶子集合的数目。
\(n\leq 2*10^5,0\leq L\leq R\leq n\)。答案对\(998244353\)取模。
题解
我们首先考虑如何计算对于\(i=0,1,2,\dots,n\),权值\(\leq i\)的集合数目。最后通过差分我们就得到了答案。
那么首先考虑如何对于一个特定的\(w\),计算权值\(\leq w\)的集合数目。
首先我们可以通过一遍\(dfs\)将根节点的权值计算出来,假设其为\(W\)。由于叶子节点的权值互不相同,因此权值和根相同的叶子是唯一的。不难发现如果我们修改了这个叶子,根节点的答案一定会变化。因此只要\(w>0\),那么所有包含这个叶子的集合的权值都是\(\leq w\)的,可以直接计入贡献。现在我们只需要考虑不包含这个叶子的集合。
我们可以发现,想要修改根的权值,有让它变大和让它变小两种选择。对于让它变大的情况,与其让它变得非常大,不如让它变成\(W+1\),因为这一定不会比变得非常大更困难。于是我们可以发现,修改所有权值\(>W\)的叶子都是没有作用的,我们只要知道如果让这个集合中能够变成\(W+1\)的权值\(<W\)的叶子都变成\(W+1\)以后,判断一下根节点是否为\(W+1\),就可以知道这个集合的权值是否\(\leq w\)。对于让根节点权值变小的情况也是同理的。那么可以发现,一个叶子集合的权值\(\leq w\)意味着可以用集合中\(<W\)的叶子让根变成\(W+1\)或者可以用集合中\(>W\)的叶子让根变成\(W-1\)。为了更方便的计算我们可以用总方案数减去两个要求都不满足的方案。那么我们只需要知道有多少个只包含\(<W\)的叶子的集合是不合法的,以及有多少个只包含\(>W\)的叶子的集合是不合法的,将两个答案相乘即可。而这个不合法的方案数又可以用总数减去合法的方案数得出来。
由于两者是相似的,因此我们以计算有多少个只包含\(<W\)的叶子的集合是合法的为例。
为了在计算中更加方便,我们将方案数转化为概率,令每个\(<W\)的叶节点有\(\frac{1}{2}\)的概率出现在集合中。那么只要让最终答案乘以\(2^{<W的叶节点数目}\)即可。令\(f_x\)表示\(x\)的权值能够\(>W\)的概率,考虑如何转移。
首先是\(x\)是\(<W\)的叶节点的情况,那么只要判断:如果\(x+w>W\),那么\(f_x=\frac{1}{2}\),否则\(f_x=0\)。
\(x\)是\(>W\)的叶节点,那么\(f_x=1\)。
假如这是一个深度为奇数的点,那么它的权值可以\(>W\)的概率就是它有任意一个儿子的权值可以\(>W\)的概率。容斥一下,可以用\(1\),减去所有儿子都不可以\(>W\)的概率。于是有:
\]
否则,这是一个深度为偶数的点,那么它的权值可以\(>W\)的概率就是它所有儿子的权值都可以\(>W\)的概率。于是有:
\]
我们发现两个转移的形式比较相似,于是考虑进一步进行简化。我们可以设\(f'_x\),当\(x\)的深度为奇数时,\(f'_x=1-f_x\),否则\(f'_x=f_x\)。不难发现此时我们的转移式就都变成了:
\]
变得更加简单。于是我们可以轻松的在\(O(n)\)的时间内求出对于某个特定的\(w\)的答案。
现在考虑快速求出所有的答案。我们发现只有当叶子节点的答案改变了,最终答案才会改变。而对于每一个叶子节点,在\(w\)增大的过程中只会发生最多一次改变。因此总的改变次数是\(O(n)\)级别的。于是我们考虑进行动态\(dp\),由于\(dp\)过程非常简单因此可以比较容易的进行动态\(dp\)。用树链剖分来实现,复杂度就是\(O(n\log^2 n)\)。
代码:
/*
There is no end though there is a start in space. ---Infinity.
It has own power, it ruins, and it goes though there is a start also in the star. ---Finite.
Only the person who was wisdom can read the most foolish one from the history.
The fish that lives in the sea doesn't know the world in the land.
It also ruins and goes if they have wisdom.
It is funnier that man exceeds the speed of light than fish start living in the land.
It can be said that this is an final ultimatum from the god to the people who can fight.
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
#include<vector>
using std::max;
using std::min;
using std::sort;
using std::greater;
using std::vector;
const int mod=998244353,inv2=(mod+1)/2;
inline int add(int a,int b)
{
return (a+=b)>=mod?a-mod:a;
}
inline int sub(int a,int b)
{
return (a-=b)<0?a+mod:a;
}
inline int mul(int a,int b)
{
return (long long)a*b%mod;
}
inline int qpow(int a,int b)
{
int res=1;
for(;b;a=mul(a,a),b>>=1)
if(b&1)
res=mul(res,a);
return res;
}
const int N=2e5+5;
struct zint
{
int v,cnt;
zint(int x=0)
{
if(x)
v=x,cnt=0;
else
v=1,cnt=1;
return;
}
zint(int _v,int _cnt)
{
v=_v;cnt=_cnt;
return;
}
inline int to_int()
{
if(cnt)
return 0;
return v;
}
inline zint operator*(const zint &th)
{
return zint(mul(v,th.v),cnt+th.cnt);
}
inline zint operator/(const zint &th)
{
return zint(mul(v,qpow(th.v,mod-2)),cnt-th.cnt);
}
};
struct epc
{
int k,b;
epc(int _k=1,int _b=0)
{
k=_k;b=_b;
return;
}
inline epc operator*(const epc &th)
{
return epc{mul(th.k,k),add(mul(th.b,k),b)};
}
}I=epc();
namespace zkw
{
int M;
epc sgt[N<<2];
inline void init(int n)
{
for(M=1;M<=n;M<<=1);
return;
}
inline void modify(int x,epc k)
{
sgt[M+x]=k;
for(x=(M+x)>>1;x;x>>=1)
sgt[x]=sgt[x<<1]*sgt[x<<1|1];
return;
}
inline epc query(int l,int r)
{
epc lres=I,rres=I;
for(l=M+l-1,r=M+r+1;l^r^1;l>>=1,r>>=1)
lres=(l&1)?lres:lres*sgt[l^1],rres=(r&1)?sgt[r^1]*rres:rres;
return lres*rres;
}
}
int n,L,R,idx;
int isl[N],key[N];
int father[N],hson[N],size[N],deep[N];
int id[N],di[N],top[N],bot[N];
zint dp[N];
vector<int> e[N];
vector<int> low,ex,high;
int ansf[N],ansg[N],ans[N];
void dfs(int x,int father,int d)
{
isl[x]=1;
if(d&1)
key[x]=0;
else
key[x]=n+1;
for(int y:e[x])
if(y!=father)
{
isl[x]=0;
dfs(y,x,d+1);
if(d&1)
key[x]=max(key[x],key[y]);
else
key[x]=min(key[x],key[y]);
}
if(isl[x])
key[x]=x;
return;
}
void dfs1(int x)
{
size[x]=1;
deep[x]=deep[father[x]]+1;
for(int y:e[x])
if(y!=father[x])
father[y]=x,dfs1(y),size[x]+=size[y],hson[x]=(size[y]>size[hson[x]]?y:hson[x]);
return;
}
void dfs2(int x,int root)
{
register int i;
id[x]=++idx;di[idx]=x;top[x]=root;
if(hson[x])
dfs2(hson[x],root);
else
for(i=x;i!=father[top[x]];i=father[i])
bot[i]=x;
for(int y:e[x])
if(y!=father[x]&&y!=hson[x])
dfs2(y,y);
return;
}
signed main()
{
int x,y,ptr,cntl=0;
epc e;
register int i;
scanf("%d%d%d",&n,&L,&R);
for(i=1;i<=n-1;i++)
scanf("%d%d",&x,&y),::e[x].push_back(y),::e[y].push_back(x);
dfs(1,0,1);
for(i=1;i<=n;i++)
if(isl[i])
{
cntl++;
if(i<key[1])
low.push_back(i);
else if(i==key[1])
ex.push_back(i);
else
high.push_back(i);
}
dfs1(1);
dfs2(1,1);
zkw::init(n);
for(i=1;i<=n;i++)
if(isl[i])
dp[i]=(key[i]>key[1])?((deep[i]&1)?0:1):((deep[i]&1)?1:0);
else
dp[i]=1;
for(i=n;i>=1;i--)
{
zkw::modify(i,epc(sub(0,dp[di[i]].to_int()),dp[di[i]].to_int()));
if(i>1&&top[di[i]]==di[i])
e=zkw::query(i,id[bot[di[i]]]),dp[father[di[i]]]=dp[father[di[i]]]*sub(1,e.b);
}
sort(low.begin(),low.end(),greater<int>());
ptr=0;
for(i=0;i<n;i++)
{
while(ptr<(int)low.size()&&key[1]+1-low[ptr]<=i)
{
x=low[ptr];
dp[x]=inv2;
while(x)
{
e=zkw::query(id[top[x]],id[bot[x]]);
if(father[top[x]])
dp[father[top[x]]]=dp[father[top[x]]]/sub(1,e.b);
zkw::modify(id[x],epc(sub(0,dp[x].to_int()),dp[x].to_int()));
e=zkw::query(id[top[x]],id[bot[x]]);
if(father[top[x]])
dp[father[top[x]]]=dp[father[top[x]]]*sub(1,e.b);
x=father[top[x]];
}
ptr++;
}
e=zkw::query(1,id[bot[1]]);
ansf[i]=sub(1,e.b);
}
ansf[n]=1;
for(i=1;i<=n;i++)
if(isl[i])
dp[i]=(key[i]<key[1])?((deep[i]&1)?1:0):((deep[i]&1)?0:1);
else
dp[i]=1;
for(i=n;i>=1;i--)
{
zkw::modify(i,epc(sub(0,dp[di[i]].to_int()),dp[di[i]].to_int()));
if(i>1&&top[di[i]]==di[i])
e=zkw::query(i,id[bot[di[i]]]),dp[father[di[i]]]=dp[father[di[i]]]*sub(1,e.b);
}
sort(high.begin(),high.end());
ptr=0;
for(i=0;i<n;i++)
{
while(ptr<(int)high.size()&&high[ptr]-key[1]+1<=i)
{
x=high[ptr];
dp[x]=inv2;
while(x)
{
e=zkw::query(id[top[x]],id[bot[x]]);
if(father[top[x]])
dp[father[top[x]]]=dp[father[top[x]]]/sub(1,e.b);
zkw::modify(id[x],epc(sub(0,dp[x].to_int()),dp[x].to_int()));
e=zkw::query(id[top[x]],id[bot[x]]);
if(father[top[x]])
dp[father[top[x]]]=dp[father[top[x]]]*sub(1,e.b);
x=father[top[x]];
}
ptr++;
}
e=zkw::query(1,id[bot[1]]);
ansg[i]=e.b;
}
ansg[n]=1;
for(i=0;i<=n;i++)
ans[i]=sub(1,mul(sub(1,ansf[i]),sub(1,ansg[i])));
x=qpow(2,cntl-1);
for(i=0;i<=n;i++)
ans[i]=mul(ans[i],x);
for(i=1;i<=n;i++)
ans[i]=add(ans[i],x);
for(i=n;i>=1;i--)
ans[i]=sub(ans[i],ans[i-1]);
ans[n]=sub(ans[n],1);
for(i=L;i<=R;i++)
printf("%d ",ans[i]);
putchar('\n');
return 0;
}
ZJOI2019 Day1 题解的更多相关文章
- THUSC2017 Day1题解
THUSC2017 Day1题解 巧克力 题目描述 "人生就像一盒巧克力,你永远不知道吃到的下一块是什么味道." 明明收到了一大块巧克力,里面有若干小块,排成n行m列.每一小块都有 ...
- 【NOIP2014】Day1题解+代码
Day1 T1 签到题,模拟一下随便写就能过. 不过小心像我一样表打错傻逼的调了10min. #include <algorithm> #include <iostream> ...
- ZJOI2019 Day1游记
退役吧垃圾 考的再烂还是要把自己捡起来 如果不想让自己的OI生涯就到这里止步的话 就给我滚去拿剩下的300分吧 浙江省前十六,学校前五,day1比别人差一百多分.如果这样还能进省队的话,我就成为传说了 ...
- Noip 2016 Day1 题解
老师让我们刷历年真题, 然后漫不经心的说了一句:“你们就先做做noip2016 day1 吧” ...... 我还能说什么,,,,,老师你这是明摆着伤害我们啊2333333333 预计分数:100+2 ...
- NOI 2016 Day1 题解
今天写了NOI2016Day1的题,来写一发题解. T2 网格 题目传送门 Description \(T\) 次询问,每次给出一个 \(n\times m\) 的传送门,上面有 \(c\) 个位置是 ...
- 十连测Day1 题解
A. 奥义商店 有一个商店,n个物品,每个物品有一个价格和一种颜色. 有m个操作,操作有两种,一种是修改一个位置的价格,另一种是购买,每次购买指定一个公差d和一个位置k,找到包含这个位置k公差为d的同 ...
- 【NOIP2013】DAY1题解+代码
T1 傻逼快速幂,敲敲就过了. 我跟你们讲个笑话当时我以为这个数据范围过不了于是想出了求GCD再推规律什么的magic方法中途还咨询了某个学长. 然后怎么想都是不可做. ……直到我发现我昨年的代码一个 ...
- NOIP 2018 day1 题解
今年noip的题和去年绝对是比较坑的题了,但是打好的话就算是普通水准也能350分以上吧. t1: 很显然这是一个简单的dp即可. #include<iostream> #include&l ...
- 【2018暑假集训模拟一】Day1题解
T1准确率 [题目描述] 你是一个骁勇善战.日刷百题的OIer. 今天你已经在你OJ 上提交了y 次,其中x次是正确的,这时,你的准确率是x/y.然而,你最喜欢一个在[0; 1] 中的有理数p/q(是 ...
随机推荐
- 原生js 数组的迭代的方法
一.原生js Array给我们提供很多了方法.方便我们操作数组.这些方法的参数,都需要传入一个匿名函数,匿名函数中有三个参数,分别含义是:数组中的项.该项的索引.以及数组本身. 1.filter方法: ...
- JAVA 第八周学习总结
20175308 2018-2019-2 <Java程序设计>第八周学习总结 教材学习内容总结 泛型 泛型的主要目的是可以建立具有类型安全的集合框架(如链表.散列映射等数据结构) 通过cl ...
- 重装系统之无法在驱动器0的分区1上安装windows
在通过U盘或光盘安装win8/win8.1/win10 时,遇到无法安装的问题,提示“无法在驱动器0的分区1上安装windows”,格式化分区也不能解决,进而提示Windows无法安装到这个磁盘,选中 ...
- ORA-00020:maximum number of processes (150) exceeded
异常的含义 超过最大的进程数 我们使用下面的语句可以查看与进程(process)的相关参数: 如上所示,这里的最大进程数是150. 问题可能存在的原因 1.应用程序在使用数据库连接池时,使用完成后没有 ...
- Python爬虫利器二之Beautiful Soup的用法
上一节我们介绍了正则表达式,它的内容其实还是蛮多的,如果一个正则匹配稍有差池,那可能程序就处在永久的循环之中,而且有的小伙伴们也对写正则表达式的写法用得不熟练,没关系,我们还有一个更强大的工具,叫Be ...
- JVM规范系列第3章:为Java虚拟机编译
Oracle 的 JDK 包括两部分内容:一部分是将 Java 源代码编译成 Java 虚拟机的指令集的编译器,另一部分是用于Java 虚拟机的运行时环境. 第一部分应该说的是 Javac 这个前置编 ...
- 如何手动写一个Python脚本自动爬取Bilibili小视频
如何手动写一个Python脚本自动爬取Bilibili小视频 国庆结束之余,某个不务正业的码农不好好干活,在B站瞎逛着,毕竟国庆嘛,还让不让人休息了诶-- 我身边的很多小伙伴们在朋友圈里面晒着出去游玩 ...
- Tomcat通过Memcached实现session共享的完整部署记录
对于web应用集群的技术实现而言,最大的难点就是:如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块.要实现这一点, 大体上有两种方式:一种是把所有Ses ...
- db2修改最大连接数
查看当前连接数,sample为数据库名db2 list applications for db sample db2 list applications for db sample show deta ...
- B树、B-树、B+树、B*树相关
B树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: B ...