【考試總結】2022-03-20
簽到
列舉哪些位置超出了限制,注意這裡需要將 \(n\) 減 \(1\),那麼可以直接列出答案的計算表示式
\[\sum_{S\subseteq U}(-1)^{|S|}\binom{n+m+(c-1)|S|-\sum\limits_{x\in S} b^{x}}{m} \]如果我們可以保證 \(A=n+m+(c-1)|S|\ge \sum\limits_{x\in S} b^{x}\),那麼組合數是關於 \(\sum\limits_{x\in S} b^x\) 的多項式
列舉 \(|S|\) 以及 \(\sum\limits_{x\in S} b^x\) 和 \(A\) 的 \(\rm LCP\)
這裡想要得到答案,還需要在低位求出關於 \(\sum b^i\) 的多項式在所有 \(S\) 中的每個次冪的和
用 \(\rm DP\) 來做:設 \(f_{k}[i][j]\) 表示考慮了 \(\{1,2\dots i\}\) 這些數字並選出了 \(j\) 個 \(b^x\) 並對第 \(k\) 次冪的自變數求和,轉移使用二項式定理合並即可
注意需要寫高精度除法,但是進位制轉換隻用做一次,那麼使用模擬豎式的方法是可行的
Code Display
int Bas=10; struct node{ vector<int> a; inline int size(){return a.size();} inline void adjust(){ int siz=a.size(); for(int i=0;i<siz-1;++i) a[i+1]+=a[i]/Bas,a[i]%=Bas; while(a[siz-1]>=Bas){ a.emplace_back(a[siz-1]/Bas); a[siz-1]%=Bas; ++siz; } while(siz>1&&!a[siz-1]) a.pop_back(),--siz; return ; } inline int toint(int Mod=mod){ int siz=a.size(),res=0; for(int i=siz-1;i>=0;--i) res=(res*Bas+a[i])%Mod; return res; } inline void init(string x){ vector<int> num[2]; num[0].resize(x.size()); int cur=0; int len=x.size(); for(int i=len-1;i>=0;--i) num[cur][i]=x[i]-'0'; reverse(num[cur].begin(),num[cur].end()); while(num[cur].size()>1||num[cur][0]>0){ num[cur^1].clear(); int tmp=0,siz=num[cur].size(); bool flag=0; for(int i=siz-1;i>=0;--i){ tmp=tmp*10+num[cur][i]; if(tmp>=Bas) flag=1; if(flag) num[cur^1].emplace_back(tmp/Bas),tmp%=Bas; } a.emplace_back(tmp); cur^=1; reverse(num[cur].begin(),num[cur].end()); siz=num[cur].size(); num[cur].emplace_back(0); for(int i=0;i<siz;++i){ num[cur][i+1]+=num[cur][i]/10; num[cur][i]%=10; } while(num[cur][siz]) num[cur].emplace_back(0),++siz,num[cur][siz]+=num[cur][siz-1]/10,num[cur][siz-1]%=10; num[cur].pop_back(); } return ; } inline void init(int x){ a.clear(); do{a.push_back(x%Bas),x/=Bas;}while(x); return ; } bool operator <(node b)const{ if(a.size()!=b.size()) return a.size()<b.size(); int len=a.size(); for(int i=len-1;~i;--i) if(a[i]^b.a[i]) return a[i]<b.a[i]; return 0; } bool operator ==(node b)const{ if(b.size()!=a.size()) return 0; int len=a.size(); for(int i=0;i<len;++i) if(a[i]!=b.a[i]) return 0; return 1; } bool operator <=(node &b)const{return *this<b||*this==b;} node operator +(node b)const{ node res; int s1=a.size(),s2=b.a.size(); res.a.resize(max(s1,s2)); for(int i=0;i<s1;++i) res.a[i]+=a[i]; for(int i=0;i<s2;++i) res.a[i]+=b.a[i]; res.adjust(); return res; } node operator -(node b)const{ node res=*this; int s1=a.size(),s2=b.size(); for(int i=0;i<s2;++i){ res.a[i]-=b.a[i]; if(res.a[i]<0) res.a[i]+=Bas,res.a[i+1]--; } for(int i=s2;i<s1;++i) if(res.a[i]<0) res.a[i+1]--,res.a[i]+=Bas; res.adjust(); return res; } node operator *(node b)const{ node res; res.a.resize(a.size()+b.size()-1); for(int i=0;i<a.size();++i) for(int j=0;j<b.size();++j) res.a[i+j]+=a[i]*b.a[j]; res.adjust(); return res; } bool operator !=(node &b){return !(*this==b);} node operator *(const int &p)const{node t; t.init(p); return *this*t;} node operator +(const int &p)const{node t; t.init(p); return *this+t;} node operator -(const int &p)const{node t; t.init(p); return *this-t;} inline void output(){ int siz=a.size(); printf("%lld ",a[siz-1]); putchar(' '); for(int i=siz-2;~i;--i) printf("%lld ",a[i]); return ; } }n; const int N=2010; int poly[N],m,b,c,ans; string str_n; int pw[N][N],dp[100][100][100]; int ifac[N],C[N][N]; signed main(){ // freopen("checkin.in","r",stdin); freopen("checkin.out","w",stdout); C[0][0]=ifac[0]=1; for(int i=1;i<=1000;++i){ C[i][0]=1; ifac[i]=mul(ifac[i-1],ksm(i,mod-2)); for(int j=1;j<=i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]); } m=read(); b=read(); c=read(); Bas=b; cin>>str_n; n.init(str_n); n=n-1; pw[0][0]=1; rep(i,1,2000) pw[0][i]=1; for(int i=1;i<=2000;++i){ pw[i][0]=1; pw[i][1]=mul(pw[i-1][1],b); for(int j=2;j<=2000;++j) pw[i][j]=mul(pw[i][j-1],pw[i][1]); } dp[0][0][0]=1; for(int k=0;k<=m;++k){ for(int i=1;i<=m;++i){ for(int j=0;j<=i;++j){ int sum=dp[i-1][j][k]; if(j) for(int t=0;t<=k;++t) ckadd(sum,mul(C[k][t],mul(dp[i-1][j-1][t],pw[i][k-t]))); dp[i][j][k]=sum; } } } for(int S=0;S<=m;++S){ node A,n_m,n_S,n_c; n_m.init(m); n_S.init(S); n_c.init(abs(1-c)); if(c<0&&n+n_m<n_c*n_S) continue; if(c<0) A=n+n_m-n_c*n_S; else A=n+n_m+n_c*n_S; int a=A.toint(); int sum=0; vector<vector<int> >tmp(m); for(int i=0;i<m;++i) tmp[i].resize(i+2); tmp[0][0]=a; tmp[0][1]=mod-1; for(int i=1;i<m;++i){ for(int j=0;j<=i+1;++j){ if(j<=i) tmp[i][j]=mul(tmp[i-1][j],del(a,i)); if(j) ckdel(tmp[i][j],tmp[i-1][j-1]); } } for(int i=0;i<=m;++i) poly[i]=mul(tmp[m-1][i],ifac[m]); auto evalue=[&](int x){ int sum=0,pw=1; for(int i=0;i<=m;++i,ckmul(pw,x)) ckadd(sum,mul(poly[i],pw)); return sum; }; int hig=0,siz=A.size(),cnt=0; for(int lcp=siz-1;~lcp&&cnt<=S;--lcp){ if(!lcp){ if(cnt==S) ckadd(sum,evalue(hig)); break; } int up=min(A.a[lcp],2ll); for(int cur=0;cur<up;++cur){ if(cur==1) ++cnt,ckadd(hig,pw[lcp][1]); if(cnt>S) break; vector<int> pwh(1+m); pwh[0]=1; for(int i=1;i<=m;++i) pwh[i]=mul(pwh[i-1],hig); int tmp=0; for(int k=0;k<=m;++k){ int sumx=0; for(int t=0;t<=k;++t) ckadd(sumx,mul(C[k][t],mul(pwh[k-t],dp[lcp-1][S-cnt][t]))); ckadd(tmp,mul(poly[k],sumx)); } ckadd(sum,tmp); } if(A.a[lcp]==1) ++cnt,ckadd(hig,pw[lcp][1]); if(A.a[lcp]>1) break; } if(S&1) ckdel(ans,sum); else ckadd(ans,sum); } print(ans); putchar('\n'); return 0; }
資料結構
將樹拍到 \(\rm DFS\) 序上考慮,序列上的帶權中點一定在帶權重心的子樹內,寫倍增判定即可
卡常,所以用了 zkw線段樹
我還有一個非常麻煩的使用 新重心在兩個原來重心的路徑上 的結論的方法,比這個垃圾到不知道哪裡去了
Code Display
vector<int> G[N]; int n; int fa[N],dep[N],siz[N],top[N],son[N],dfn[N],ord[N],tim; int bz[N][20]; inline void dfs2(int x,int topf){ top[x]=topf; ord[dfn[x]=++tim]=x; if(son[x]) dfs2(son[x],topf); for(auto t:G[x]) if(!dfn[t]) dfs2(t,t); } inline void dfs1(int x,int fat){ dep[x]=dep[fa[x]=fat]+(siz[x]=1); bz[x][0]=fat; for(int i=1;bz[x][i-1];++i) bz[x][i]=bz[bz[x][i-1]][i-1]; for(auto t:G[x]) if(t!=fat){ dfs1(t,x); siz[x]+=siz[t]; if(siz[t]>siz[son[x]]) son[x]=t; } return ; } ll nec; struct Seg{ #define ls p<<1 #define rs p<<1|1 int lim; ll sum[N<<2],tag[N<<2]; inline void build(int n){ lim=1; while(lim<=(n+1)) lim<<=1; return ; } inline void upd(int l,int r,ll v){ l+=lim-1; r+=lim+1; int lc=0,rc=0; for(int len=1;l!=r-1;l>>=1,r>>=1,len<<=1){ sum[l]+=lc*v; sum[r]+=rc*v; if(!(l&1)) sum[l^1]+=len*v,tag[l^1]+=v,lc+=len; if(r&1) sum[r^1]+=len*v,tag[r^1]+=v,rc+=len; } while(l){ sum[l]+=lc*v; sum[r]+=rc*v; l>>=1; r>>=1; } return ; } inline ll Query(int l,int r){ ll res=0,rc=0,lc=0; l+=lim-1; r+=lim+1; for(int len=1;l!=r-1;r>>=1,l>>=1,len<<=1){ res+=tag[l]*lc+tag[r]*rc; if(r&1) res+=sum[r^1],rc+=len; if(!(l&1)) res+=sum[l^1],lc+=len; } while(l){ res+=tag[l]*lc+tag[r]*rc; l>>=1; r>>=1; } return res; } #undef ls #undef rs #undef lson #undef rson }T; signed main(){ freopen("yyl.in","r",stdin); freopen("yyl.out","w",stdout); n=read(); rep(i,1,n-1){ int u=read(),v=read(); G[u].push_back(v); G[v].emplace_back(u); } int Q=read(); dfs1(1,0); dfs2(1,1); T.build(n); while(Q--){ if(read()-1){ int u=read(),v=read(),w=read(); while(top[u]!=top[v]){ if(dep[top[u]]<dep[top[v]]) swap(u,v); T.upd(dfn[top[u]],dfn[u],w); u=fa[top[u]]; } if(dep[u]>dep[v]) swap(u,v); T.upd(dfn[u],dfn[v],w); }else{ int x=read(),w=read(); T.upd(dfn[x],dfn[x]+siz[x]-1,w); } nec=T.sum[1]/2+1; //找到第一個大於等於 nec 的點即可 int l=1,r=n-1,pos=n; while(l<=r){ int mid=(l+r)>>1; if(T.Query(1,mid)>=nec) pos=mid,r=mid-1; else l=mid+1; } int ans=ord[pos]; for(int i=19;~i;--i) if(bz[ans][i]){ int tar=bz[ans][i]; if(T.Query(dfn[tar],dfn[tar]+siz[tar]-1)<=T.sum[1]/2) ans=bz[ans][i]; } if(T.Query(dfn[ans],dfn[ans]+siz[ans]-1)<=T.sum[1]/2) ans=fa[ans]; print(ans); } return 0; }
爆搜
觀察到 \(n\) 個點的無向圖最多有 \(\frac n2\) 組匹配,將 \((2i,2i+1)\) 連一條虛邊,那麼所有的合法匹配一定將點集劃分成了若干個環和鏈
使用虛邊將 \((2i,2i+1)\) 繫結成一個虛點,使用 \(\Theta(2^{\frac n2}n^2)\) 的狀壓 \(\rm DP\) 來求每個集合表示成鏈/環的權值之和,剩下的工作是一個集合冪級數 \(\exp\)
\(\rm DP\) 鏈新增一維記錄鏈尾,轉移時因為 \((2i,2i+1)\) 被繫結,新增一組虛點的時候可以據此得到新的鏈尾,注意每條鏈會被計算正反兩次
計算環的方案數時從環上最大標號的點開始 \(\rm DP\),加入的一對虛點標號不超過當前集合最大值即可,也要記錄環尾,最後通過環尾和集合內最大值是否有邊判定是否合法即可
Code Display
const int N=40;
int n,m,C,G[N][N],inv[N],ifac[N],fac[N];
int chain[1<<18],cyc[1<<18],dp[1<<18][40];
inline int in(int S,int id){
return S>>(id>>1)&1;
}
struct FPS{
int p[19]; FPS(){memset(p,0,sizeof(p));}
FPS operator +(const FPS &a)const{
FPS res;
rep(i,0,m) res.p[i]=add(p[i],a.p[i]);
return res;
}
FPS operator -(const FPS &a)const{
FPS res;
rep(i,0,m) res.p[i]=del(p[i],a.p[i]);
return res;
}
}f[1<<18];
inline void Exp(int *f){
vector<int> g(m+1);
g[0]=1;
for(int i=1;i<=m;++i){
for(int j=0;j<i;++j) ckadd(g[i],mul(f[j+1],mul(g[i-j-1],j+1)));
ckmul(g[i],inv[i]);
}
for(int i=0;i<=m;++i) f[i]=g[i];
return ;
}
signed main(){
freopen("dfs.in","r",stdin); freopen("dfs.out","w",stdout);
n=39; inv[0]=fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
ifac[n]=ksm(fac[n],mod-2);
Down(i,n,1) ifac[i-1]=mul(ifac[i],i),inv[i]=mul(ifac[i],fac[i-1]);
n=read(); m=read(); C=read(); if(n&1) ++n;
while(m--){
int u=read()-1,v=read()-1;
G[u][v]++; G[v][u]++;
}
m=(n+1)/2;
int S=1<<m;
for(int i=0;i<m;++i){
dp[1<<i][i<<1]=1;
if(2*i+1<=n) dp[1<<i][i<<1|1]=1;
}
for(int i=0;i<S;++i){
for(int j=0;j<n;++j) if(dp[i][j]){
for(int k=0;k<n;++k) if(!in(i,k)&&G[j][k]){
ckadd(dp[i^(1<<(k>>1))][k^1],mul(C,dp[i][j]));
}
}
}
for(int i=1;i<S;++i){
for(int j=0;j<n;++j) ckadd(chain[i],dp[i][j]);
ckmul(chain[i],inv[2]);
}
memset(dp,0,sizeof(dp));
for(int i=0;i<m;++i) dp[1<<i][i<<1]=1;
for(int i=1;i<S;++i){
int hig=63-__builtin_clzll(i);
for(int j=0;j<=hig*2+1;++j) if(dp[i][j]){
for(int k=0;k<=hig*2+1;++k) if(!in(i,k)&&G[j][k]){
ckadd(dp[i^(1<<(k>>1))][k^1],mul(C,dp[i][j]));
}
}
}
for(int i=1;i<S;++i){
int hig=63-__builtin_clzll(i);
for(int j=0;j<=2*hig+1;++j) ckadd(cyc[i],mul(G[j][hig<<1|1],dp[i][j]));
ckmul(cyc[i],C);
}
for(int i=0;i<S;++i) f[i].p[__builtin_popcount(i)]=add(cyc[i],chain[i]);
for(int p=2;p<=S;p<<=1){
int len=p>>1;
for(int k=0;k<S;k+=p) for(int l=k;l<k+len;++l) f[l+len]=f[l+len]+f[l];
}
for(int i=0;i<S;++i) Exp(f[i].p);
for(int p=2;p<=S;p<<=1){
int len=p>>1;
for(int k=0;k<S;k+=p) for(int l=k;l<k+len;++l) f[l+len]=f[l+len]-f[l];
}
print(f[S-1].p[m]);
return 0;
}