[考试反思]0422省选模拟77:空白
没提交没上榜。$17$的暴力懒得写了,和爆零没啥区别。
最后半小时狂飙$T3$的$45pts$的部分分,然而写错一个变量名,最终没有调出来。
(然而因为$OJ$的感人速度所以就算调出来了也只有$20pts$,谁知道出题人这诡异的$10^4$到底让不让$O(n^2)$过)
$T1$是个之前用过的套路,然而我根本都没往$dp$上想。
反而联想到了之前某个类似(然而不一样)的题,当时那题是记忆化搜索有不少细节。
所以这次花了不少时间又写了个记搜,又一次调了半天,结果最后特别慢,第一档数据都过不去。
然后$T3$的$45pts$思路是对的,最后还是着急了没写出来(其实也有担心出题人卡$O(n^2)$的因素吧)
另:感觉写题解的人不会说人话
T1:抽鬼牌
大意:求有多少$n$排列满足不存在长度超过$m$的以$1/-1$为公差的等差数列。$n,m \le 200$
非常不会应付 排列 类的题。
这道题的套路是:把排列按照题目规则划分为若干段,并且记录每一段中元素相对排名(而不是具体值)
这样就可以很方便的插入,添加一个新的段,最终构造出长度为$n$的序列,它就是一个排列。
放到这道题上,就是:对于任意一个排列,我们按照极长的等差数列将其划分为若干段。
设$dp[i][j]$表示目前构成的序列总长是$i$然后已经有了$j$段的方案数。
如果不考虑相邻段可以构成一个大段,那么有如下转移:
$dp[i][j]+=dp[x][j-1] \times j \times (1+[i-x>1])$
其中乘$j$表示最新的这一段所处的值域在所有段中的相对排名,后面那个表示如果这个段长度大于$1$那么就可以正反两种放法
问题在于,一段可能会被你拆开两次进行计数。所以设计一个容斥系数来干掉它。
这个容斥系数显然只与你整个段的长度有关。
$dp[i][j]+=dp[x][j-1] \times j \times (1+[i-x>1]) \times f(i-x)$
$f(i) = - \sum\limits_{j=1}^{m} f(i-j)$
就是枚举最后一个子段的长度,那么如果有$T$个子段就会被计算$(-1)^{T-1}$次。容斥完全。
时间复杂度$O(n^3)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 998244353 4 int dp[222][222],f[222],n,m,ans; 5 int main(){ 6 cin>>n>>m; 7 f[0]=mod-1;dp[0][0]=1; 8 for(int i=0;i<=n;++i)for(int j=1;j<=m&&i+j<=n;++j)f[i+j]=(f[i+j]+mod-f[i])%mod; 9 for(int r=1;r<=n;++r)for(int i=1;i<=n;++i)for(int l=0;l<r;++l) 10 dp[i][r]=(dp[i][r]+(1ll+(r>l+1))*dp[i-1][l]*i%mod*f[r-l])%mod; 11 for(int i=1;i<=n;++i)ans=(ans+dp[i][n])%mod; cout<<ans<<endl; 12 }
T2:队列
大意:求${w_1,w_2,...w_n}$的多重集排列满足 不存在任意前缀和后缀中所有种类的元素都出现过且出现次数也都一样。方案数。$w_1 \le 10^6,n \le 10$
我们把每种数出现次数的前缀和写成$n$维向量的形式。
那么题意转化为:从$(0,0,...,0)$走到$(w_1,w_2,...,w_n)$,中途不经过$(i,i,...,i)$这样或$(w_1-i,w_2-i,...,w_n-i)$这样的点
把所有点排序之后做容斥$dp$,经典的$dp[i]$表示从起点到$i$不经过其他特殊点的路径有多少然后枚举钦定经过的第一个特殊点再减掉方案数。
这样可以写出一个$O(n^2)$的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 998244353 4 int n,w[11],fac[10000007],inv[10000007],tot,dp[10005],pc; 5 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 6 struct ps{ 7 int x[11]; 8 friend bool operator<(ps a,ps b){ 9 for(int i=1;i<=n;++i)if(a.x[i]!=b.x[i])return a.x[i]<b.x[i]; 10 return 0; 11 } 12 }P[55555]; 13 int cal(int x,int y){ 14 int a=1,tot=0; 15 for(int i=1;i<=n;++i)if(P[y].x[i]<P[x].x[i])return 0; 16 else tot+=P[y].x[i]-P[x].x[i],a=1ll*a*inv[P[y].x[i]-P[x].x[i]]%mod; 17 return a*1ll*fac[tot]%mod; 18 } 19 int main(){ 20 cin>>n; 21 for(int i=1;i<=n;++i)cin>>w[i],tot+=w[i]; 22 sort(w+1,w+1+n); 23 for(int i=fac[0]=1;i<=tot;++i)fac[i]=fac[i-1]*1ll*i%mod; 24 inv[tot]=qp(fac[tot],mod-2);cerr<<inv[tot]*1ll*fac[tot]%mod<<endl; 25 for(int i=tot-1;~i;--i)inv[i]=inv[i+1]*(i+1ll)%mod; 26 for(int i=0;i<=w[1];++i){pc++;for(int j=1;j<=n;++j)P[pc].x[j]=i;} 27 for(int i=0;i<=w[1];++i){pc++;for(int j=1;j<=n;++j)P[pc].x[j]=w[j]-i;} 28 sort(P+1,P+1+pc); 29 for(int i=1;i<=pc;++i){ 30 dp[i]=cal(1,i); 31 for(int j=2;j<i;++j)dp[i]=(dp[i]-1ll*cal(j,i)*dp[j]%mod+mod)%mod; 32 }cout<<dp[pc]<<endl; 33 }
接下来为了方便,把所有的$w$按照从小到大排序。显然排序不会影响答案。
考虑这道题的特殊性,点只有两种形式,$(i,i,i)$或者$(w_1-i,w_2-i,w_n-i)$。每种点$w_1+1$个(含端点)
我们把这两类点分别按照第一维坐标排序,那么两组的第$i$个点第一维坐标都是$i$
$f:(0,0,0)(1,1,1)(2,2,2)...(w[1],w[1],w[1])$
$g:(0,w[2]-w[1],w[3]-w[1])(1,w[2]-w[1]+1,w[3]-w[1]+1)...(w[1],w[2],w[3])$
大概就是这个意思。发现$f \rightarrow g,g \rightarrow g,f \rightarrow f,g \rightarrow f$的转移都是卷积形式。
如果用分治$FFT$可以$O(nlog^2n)$
构造生成函数$F,G$。构造转移系数的生成函数$f \rightarrow g:B,g \rightarrow g:A,f \rightarrow f:A,g \rightarrow f:C$
那么就有:$F=A-FA-GC,G=B-FB-GA$。移项前面一个式子得到$F=\frac{A-GC}{A+1}$。
把这个东西代入$G$并移项得到$G=\frac{B}{(A+1)^2-BC}$
其中$A,B,C$都可以在$O(\sum w_i)$的复杂度构造出来,然后只需要一个多项式求逆就好了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 998244353 4 #define S 1<<22 5 int rev[S],A[S],B[S],C[S],R[S],r[S],tot,w[11],n,len,fac[10000001],inv[10000001],T[S],G[S]; 6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 7 int mo(int x){return x>=mod?x-mod:x;} 8 void sat(int x){ 9 len=1;while(len<x<<1)len<<=1; 10 for(int i=1;i<len;++i)rev[i]=rev[i>>1]>>1|(i&1?len>>1:0); 11 } 12 void NTT(int*a,int op=1){ 13 for(int i=1;i<len;++i)if(rev[i]>i)swap(a[i],a[rev[i]]); 14 for(int i=1;i<len;i<<=1)for(int j=0,w=qp(3,(mod-1)/2/i*op+mod-1);j<len;j+=i<<1) 15 for(int k=j,x,y,t=1;k<j+i;++k,t=1ll*t*w%mod) 16 x=a[k],y=1ll*a[k+i]*t%mod,a[k]=mo(x+y),a[k+i]=mo(x-y+mod); 17 if(op==-1)for(int iv=qp(len,mod-2),i=0;i<len;++i)a[i]=1ll*a[i]*iv%mod; 18 } 19 void Inv(int*a,int n){ 20 if(n==1){r[0]=qp(a[0],mod-2);return;} 21 Inv(a,n+1>>1); sat(n); 22 for(int i=0;i<len;++i)T[i]=i<n?a[i]:0; 23 NTT(T);NTT(r); for(int i=0;i<len;++i)r[i]=(r[i]+r[i]-1ll*T[i]*r[i]%mod*r[i]%mod+mod)%mod; NTT(r,-1); 24 for(int i=n;i<len;++i)r[i]=0; 25 } 26 int main(){ 27 cin>>n; for(int i=1;i<=n;++i)cin>>w[i],tot+=w[i]; 28 sort(w+1,w+1+n); 29 for(int i=fac[0]=1;i<=tot;++i)fac[i]=fac[i-1]*1ll*i%mod; 30 inv[tot]=qp(fac[tot],mod-2); 31 for(int i=tot;i;--i)inv[i-1]=inv[i]*1ll*i%mod; 32 33 for(int i=0;i<=w[1];++i){ 34 A[i]=fac[i*n]; B[i]=fac[tot-w[1]*n+i*n]; C[i]=i>=w[n]-w[1]?fac[i*n-tot+w[1]*n]:0; 35 for(int j=1;j<=n;++j)A[i]=A[i]*1ll*inv[i]%mod,B[i]=B[i]*1ll*inv[w[j]+i-w[1]]%mod,C[i]=C[i]*1ll*inv[w[1]-w[j]+i]%mod; 36 } 37 38 sat(w[1]+1);NTT(A);NTT(B);NTT(C); 39 for(int i=0;i<len;++i)R[i]=mo((1ll*A[i]*A[i]-1ll*B[i]*C[i])%mod+mod); NTT(R,-1); 40 Inv(R,w[1]+1); 41 sat(w[1]+1); NTT(r); 42 for(int i=0;i<len;++i)G[i]=r[i]*1ll*B[i]%mod; NTT(G,-1); 43 cout<<G[w[1]]<<endl; 44 }
T3:乙姬心恋爱宫殿
大意:树点有颜色,求有多少树上无交点路径对满足每条路径端点颜色相同。对于每个$i$,要求路径端点不能是$i$后再次回答该问题。$n,c_i \le 10^5$
首先$O(n^2 max(c))$的暴力比较好想。
我们枚举一个点作为路径的端点,然后$dp$一下求出以它为路径端点的方案数$ans_i$
那么总方案数就是$tot=\frac{\sum ans_i}{4}$
要求点$i$不能选的方案数就是$tot-ans_i$
具体$dp$过程的话,就记录一下$cnt[i][j]$表示$i$子树内有多少个颜色$j$,$mt[i]$表示$i$子树内有多少完整路径。
这就可以转移并统计答案了。$O(n^2max(c))$然而常数很大,$n=10^4,c=1$都过不去(所以出题人出这档部分分干啥,时限也应该开到$10s?$
1 #include<bits/stdc++.h> 2 using namespace std; 3 int cnt[10001][1001],n,m,q,co[10001],r[10001],fir[10001],l[20001],to[20001],ec,mt[10001],s,f2[10001]; 4 long long ans[10001]; 5 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 6 void dfs(int p,int fa){//cerr<<p<<' '<<fa<<endl; 7 for(int i=q;i;--i)cnt[p][i]=0; mt[p]=0; 8 for(int i=fir[p],y;y=to[i];i=l[i])if(y!=fa){ 9 dfs(y,p); mt[p]+=mt[y]; 10 for(int j=q;j;--j)cnt[p][j]+=cnt[y][j]; 11 } 12 if(mt[p])for(int i=fir[p],y;y=to[i];i=l[i])if(y!=fa)ans[s]+=(mt[p]-mt[y])*cnt[y][co[s]]; 13 if(co[p]==co[s]&&p!=s)ans[s]+=mt[p]; 14 cnt[p][co[p]]++; mt[p]=0; 15 for(int j=q;j;--j)if(cnt[p][j]>1)mt[p]+=f2[cnt[p][j]]; 16 } 17 int main(){//freopen("palace3.in","r",stdin);freopen("0.out","w",stdout); 18 cin>>n>>m>>q; 19 for(int i=1;i<=n;++i)scanf("%d",&co[i]),r[i]=co[i]; 20 for(int i=1;i<=n;++i)f2[i]=i*(i-1)>>1; 21 sort(r+1,r+1+n);q=unique(r+1,r+1+n)-r-1; 22 for(int i=1;i<=n;++i)co[i]=lower_bound(r+1,r+1+q,co[i])-r; 23 for(int i=1,a,b;i<n;++i)scanf("%d%d",&a,&b),link(a,b),link(b,a); 24 for(int i=1;i<=n;++i)dfs(s=i,0); 25 long long tot=0; 26 for(int i=1;i<=n;++i)tot+=ans[i];tot>>=2; 27 cout<<tot%1000000007<<endl; 28 int x;while(m--)scanf("%d",&x),printf("%lld\n",(tot-ans[x])%1000000007); 29 }
看到多颜色点且只有同颜色会产生关系,不难想到虚树。
对于每种颜色构建出虚树,预处理虚边的转移系数(子树内有多少路径)。大约是可以树剖。
然后只要一个麻烦的换根dp$就可以了。大约是没时间实现了。