====== Atcoder Rugular Contest 115 ====== [[https://atcoder.jp/contests/arc115|比赛链接]] ===== D - Odd Degree ===== ==== 题意 ==== 给定一个图,询问满足度数为奇数的点的个数恰好为 $k(1\sim n)$ 的生成图数量。 其中生成图的定义为原图的点集 $+$ 原图的边集的子集。 ==== 题解 ==== 首先考虑图是树的情况,有结论:如果 $k$ 为奇数,显然生成图不存在;如果 $k$ 为偶数,则生成图数量为 ${n\choose k}$。 下面用数学归纳发证明:对所有 $n\ge 1$,任意指定树上 $k(2\mid k)$ 个点作为度数为奇数的结点则满足条件的生成图唯一: $n=1$ 时只有 $k=0$ 的情况,显然生成图唯一。 下面考虑从 $n+1$ 个点的树上任意指定 $k(2\mid k)$ 个点作为度数为奇数的结点。 任取一个度数为 $1$ 的点,如果该点被指定为度数为奇数的点,则该点与树的连边一定保留。 考虑无视该点和该边,则等价于与该点相邻的另一个点在生成树中的奇偶性改变,于是有 $k'=k$ 或 $k'=k-2$。 如果该点被指定为度数为偶数的点,则该点与树的连边一定不保留,考虑无视该点和该边,于是有 $k'=k$。 然后等价于找 $n$ 个点中有 $k’$ 个点的生成图,根据数学归纳法,这是唯一的。 接下来考虑连通图的情况,假设图中有 $n$ 个点和 $m$ 条边,先确定一棵生成树。 对于非树边,有选于不选两种方案,同时非树边选与不选同时改变两个点的奇偶性。 当 $k$ 为偶数时,任意指定 $k$ 个点为度数为奇数的点,先考虑所有非树边选与不选对所有点奇偶性的影响,共 $2^{m-n+1}$ 种情况。 最后等价于构造图有 $k'$ 个点是度数为奇数的点的生成树的子图,此时方案数唯一。于是总方案数为 $2^{m-n+1}{n\choose k}$。 对于不连通图,考虑分治 $\text{NTT}$ 处理背包的合并,时间复杂度 $O(n\log^2 n)$。 const int Mod=998244353,MAXN=5005; int quick_pow(int a,int k){ int ans=1; while(k){ if(k&1)ans=1LL*ans*a%Mod; a=1LL*a*a%Mod; k>>=1; } return ans; } namespace Poly{ const int G=3; int rev[MAXN<<2]; int build(int k){ int n,pos=0; while((1<>1]>>1)|((i&1)<<(pos-1)); return n; } void NTT(int *f,int n,bool type){ _for(i,0,n)if(i g[MAXN]; int blk_id[MAXN],blk_cnt; int pool[MAXN<<1],blk_sz[MAXN],blk_edge[MAXN],*blk_s[MAXN]; void dfs(int u){ blk_id[u]=blk_cnt; _for(i,0,g[u].size()){ int v=g[u][i]; if(blk_id[v])continue; dfs(v); } } void solve(int lef,int rig){ if(lef==rig)return; int mid=lef+rig>>1; solve(lef,mid); solve(mid+1,rig); int len1=1,len2=1; _rep(i,lef,mid)len1+=blk_sz[i]; _rep(i,mid+1,rig)len2+=blk_sz[i]; Poly::Mul(blk_s[lef],len1,blk_s[mid+1],len2); } int main() { frac[0]=pow2[0]=1; _for(i,1,MAXN)frac[i]=1LL*i*frac[i-1]%Mod,pow2[i]=2LL*pow2[i-1]%Mod; inv[MAXN-1]=quick_pow(frac[MAXN-1],Mod-2); for(int i=MAXN-1;i;i--)inv[i-1]=1LL*inv[i]*i%Mod; int n=read_int(),m=read_int(); _for(i,0,m){ int u=read_int(),v=read_int(); g[u].push_back(v); g[v].push_back(u); } _rep(i,1,n){ if(!blk_id[i]){ blk_cnt++; dfs(i); } blk_sz[blk_id[i]]++; blk_edge[blk_id[i]]+=g[i].size(); } int *pos=pool; _rep(i,1,blk_cnt){ blk_s[i]=pos; pos+=blk_sz[i]+1; int base=pow2[blk_edge[i]/2-blk_sz[i]+1]; _rep(j,0,blk_sz[i]){ if(j%2==0) blk_s[i][j]=1LL*C(blk_sz[i],j)*base%Mod; else blk_s[i][j]=0; } } solve(1,blk_cnt); _rep(i,0,n) enter(blk_s[1][i]); return 0; } ===== E - LEQ and NEQ ===== ==== 题意 ==== 给定序列 $A$,求所有满足下列条件的序列 $X$ 数目: - $1\le X_i\le A_i$ - $X_i \neq X_{i+1}$ ==== 题解 ==== 设 $\text{dp}(i,j)$ 表示满足限制条件且 $X_i=j$ 的序列 $X_{1\sim i}$ 的数目,$S_i=\sum_{j=1}^{A_i}\text{dp}(i,j)$。 于是对于 $j\le \min(A_i,A_{i+1})$,有 $\text{dp}(i+1,j)=S_i-\text{dp}(i,j)$。 如果 $A_i\lt A_{i+1}$,对于 $A_i\lt j\le A_{i+1}$,有 $\text{dp}(i+1,j)=S_i$。 于是维护一棵支持区间取负,区间加,区间赋值的线段树即可,时间复杂度 $O(n\log n)$。 const int MAXN=5e5+5,Mod=998244353; int a[MAXN],b[MAXN],lef[MAXN<<2],rig[MAXN<<2],s[MAXN<<2],addTag[MAXN<<2],signTag[MAXN<<2],setTag[MAXN<<2]; bool setFlag[MAXN<<2]; void build(int k,int L,int R){ lef[k]=L,rig[k]=R,signTag[k]=1; if(L==R) return; int M=L+R>>1; build(k<<1,L,M); build(k<<1|1,M+1,R); } int getLen(int k){ return b[rig[k]]-b[lef[k]-1]; } void toSet(int k,int v){ s[k]=1LL*getLen(k)*v%Mod; addTag[k]=0; signTag[k]=1; setTag[k]=v; setFlag[k]=true; } void toNeg(int k){ s[k]=(Mod-s[k]); if(setFlag[k]) setTag[k]=(Mod-setTag[k])%Mod; else{ signTag[k]*=-1; addTag[k]=(Mod-addTag[k])%Mod; } } void toAdd(int k,int v){ s[k]=(s[k]+1LL*getLen(k)*v)%Mod; if(setFlag[k]) setTag[k]=(setTag[k]+v)%Mod; else addTag[k]=(addTag[k]+v)%Mod; } void push_down(int k){ if(setFlag[k]){ toSet(k<<1,setTag[k]); toSet(k<<1|1,setTag[k]); setFlag[k]=false; } else{ if(signTag[k]==-1){ toNeg(k<<1); toNeg(k<<1|1); signTag[k]=1; } if(addTag[k]){ toAdd(k<<1,addTag[k]); toAdd(k<<1|1,addTag[k]); addTag[k]=0; } } } void push_up(int k){ s[k]=(s[k<<1]+s[k<<1|1])%Mod; } void update1(int k,int L,int R,int v){ if(L<=lef[k]&&rig[k]<=R){ toSet(k,v); return; } push_down(k); int mid=lef[k]+rig[k]>>1; if(mid>=L) update1(k<<1,L,R,v); if(mid>1; if(mid>=L) update2(k<<1,L,R,v); if(mid>1; if(mid>=R) return query(k<<1,L,R); else if(mid