[考试反思]0326省选模拟55:摸索
神仙题系列。
$T1$想的差不多了。但是最后一步垮了。(线段树有个弱智问题想了太久
和正解非常像,但是最后写出来还不如$O(n^2)$的。
$T2$除非写一个特别麻烦的搜索不然都没有分。而搜索也只有$10$分。算了吧
$T3$其实$dp$定义已经想到了,但是时间不够于是没有继续深入。
时间不够,于是在$20$分状压和一种奇怪的乱搞之中选择了乱搞,然后就爆零了。
然后总分就是这么惨淡。在也不相信部分分了。。。
但是其实题目质量还不错($T2$太结论了)有空得去补一补博弈论了。
T1:调兵遣将
大意:数列。定义一种合法方案是在整个数列中选出若干子区间,互不相交且所有子区间的$gcd$相同。求每个下标在多少种合法方案中被包含于一个区间内。$n \le 50000,a_i \le 10^9$
首先,正难则反,我们不会求被包含,那么我们就求(总方案-不被任何一个区间包含的方案)。
区间$gcd$极度恶心。但是我们知道对于确定的右端点,$gcd$只有$O(log)$种。
求出所有$gcd$区间之后丢到对应的桶里,现在就只需要考虑对于一种$gcd$其各个区间的关系了。
然后考虑一个$dp$。设$f[i]$表示上一个子串结束位置$\le i$的方案数。
假如我们把上面求$gcd$得到的结果用$(l_1,l_2,r)$表示。那么按照$r$排序之后依次加入,转移是:
$f[r...n] += \sum\limits_{L=l_1}^{l_2} f[L-1]$。就是说,依次加入$[l_1,r],[l_1+1,r],...[l_2,r]$这些子串,贡献是$f[L-1]$。而右端点是$r$
同理求出一个$g$表示倒着加入,上一个子串左端点$\ge i$的方案数。
$f[n]$即为总方案数。对于每个点$i$,不包含它的方案数就是$f[i-1]\times g[i+1]$
想到这里你就有$30$分了。开心吧?
然而我们发现,我们把每个区间的固定点(右端点固定时左端点是个区间,反之亦然)都拿出来排序,然后相邻两个关键点之间的部分,它们的$f,g$均相同,故$f\times g$也相同。
所以只用对于每个间隙查询。时间复杂度为$O(nlog^2n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 111111 4 #define mod 998244353 5 map<int,int>M; 6 vector<int>L1[S<<3],L2[S<<3],R[S<<3],L[S<<3],R1[S<<3],R2[S<<3]; 7 int n,w[S],q[S],v[S],rq[S],rv[S],cnt,tot,pts[S],ptc; 8 int gcd(int a,int b){return b?gcd(b,a%b):a;} 9 int mo(int x){return x>=mod?x-mod:x;} 10 struct Tree{ 11 int tv[S<<2],lz[S<<2]; 12 #define lc p<<1 13 #define rc lc|1 14 #define md (cl+cr>>1) 15 void clear(int p=1,int cl=0,int cr=n+1){ 16 if(!tv[p])return; 17 tv[p]=lz[p]=0; 18 if(cl!=cr)clear(lc,cl,md),clear(rc,md+1,cr); 19 } 20 void add(int w,int l,int r,int p=1,int cl=0,int cr=n+1){ 21 if(l<=cl&&cr<=r){lz[p]=mo(lz[p]+w);tv[p]=(tv[p]+w*(cr-cl+1ll))%mod;return;} 22 if(lz[p])lz[lc]=mo(lz[lc]+lz[p]),lz[rc]=mo(lz[rc]+lz[p]),tv[lc]=(tv[lc]+(md-cl+1ll)*lz[p])%mod,tv[rc]=(tv[rc]+1ll*(cr-md)*lz[p])%mod,lz[p]=0; 23 if(l<=md)add(w,l,r,lc,cl,md); if(r>md)add(w,l,r,rc,md+1,cr); 24 tv[p]=mo(tv[lc]+tv[rc]); 25 } 26 int ask(int l,int r,int p=1,int cl=0,int cr=n+1){ 27 if(l<=cl&&cr<=r)return tv[p]; 28 if(lz[p])lz[lc]=mo(lz[lc]+lz[p]),lz[rc]=mo(lz[rc]+lz[p]),tv[lc]=(tv[lc]+(md-cl+1ll)*lz[p])%mod,tv[rc]=(tv[rc]+1ll*(cr-md)*lz[p])%mod,lz[p]=0; 29 return mo((l<=md?ask(l,r,lc,cl,md):0)+(r>md?ask(l,r,rc,md+1,cr):0)); 30 } 31 }ll,rr,ans; 32 int main(){//freopen("ex_A2.in","r",stdin); 33 cin>>n; 34 for(int i=1;i<=n;++i)scanf("%d",&w[i]); 35 for(int i=1,t=0,rt;i<=n;++i){ 36 q[++t]=i;v[t]=w[i]; 37 for(int j=1;j<t;++j)v[j]=gcd(v[j],w[i]); rt=v[t+1]=0; 38 for(int j=1;j<=t;++j)if(v[j]!=v[j+1])rq[++rt]=q[j],rv[rt]=v[j]; 39 for(int j=1;j<=rt;++j){ 40 q[j]=rq[j],v[j]=rv[j]; int o; 41 o=M[v[j]];if(!o)M[v[j]]=o=++cnt; 42 L1[o].push_back(q[j-1]+1); L2[o].push_back(q[j]); R[o].push_back(i); 43 }t=rt; 44 }q[0]=n+1; 45 for(int i=n,t=0,rt;i;--i){ 46 q[++t]=i;v[t]=w[i]; 47 for(int j=1;j<t;++j)v[j]=gcd(v[j],w[i]); rt=v[t+1]=0; 48 for(int j=1;j<=t;++j)if(v[j]!=v[j+1])rq[++rt]=q[j],rv[rt]=v[j]; 49 for(int j=1;j<=rt;++j){ 50 q[j]=rq[j],v[j]=rv[j]; int o; 51 o=M[v[j]];if(!o)M[v[j]]=o=++cnt; 52 R2[o].push_back(q[j-1]-1); R1[o].push_back(q[j]); L[o].push_back(i); 53 }t=rt; 54 } 55 for(int i=1;i<=cnt;++i){ 56 ptc=2;pts[1]=1;pts[2]=n; 57 rr.clear(); rr.add(1,0,n+1); 58 for(int j=0;j<R[i].size();++j)rr.add(rr.ask(L1[i][j],L2[i][j]),R[i][j]+1,n+1),pts[++ptc]=R[i][j]; 59 tot=mo(tot+rr.ask(n+1,n+1)); 60 ll.clear(); ll.add(1,0,n+1); 61 for(int j=0;j<L[i].size();++j)ll.add(ll.ask(R1[i][j],R2[i][j]),0,L[i][j]-1),pts[++ptc]=L[i][j]; 62 sort(pts+1,pts+1+ptc); 63 for(int j=1,p;j<=ptc;++j)if(pts[j]!=pts[j-1]){ 64 p=pts[j];ans.add(1ll*ll.ask(p,p)*rr.ask(p,p)%mod,p,p); 65 if(pts[j]>pts[j-1]+1)p=pts[j-1]+1,ans.add(1ll*ll.ask(p,p)*rr.ask(p,p)%mod,p,pts[j]-1); 66 } 67 }for(int i=1;i<=n;++i)printf("%d ",mo(tot+mod-ans.ask(i,i)));//cerr<<endl<<cnt<<endl; 68 }
T2:一掷千金
大意:树。每个点编号为二维坐标$(x,y)$。其父亲为$(max(x-1,0),max(y-1,0))$。$(0,0)$为根。
最开始的时候$n$个矩阵的点都是亮的。每次操作可以选定一个亮的点,使祖先链全部点亮暗状态改变。不能操作者败。求$SG$值。
$n,x \le 10^5,y\le 10^9$
挺经典的翻硬币模型,所以满足:对于整个局面中所有亮的点,可以把它孤立出来,把它和祖先链单独作为一场游戏考虑$SG$值。(祖先链都是暗的)
然后对于所有的初始亮点,我们把所有亮点的SG值异或起来,就是总局面的$SG$值。
上述结论均可用归纳法大致证明(看的不太明白)。
附个为数不多的带证明的链接。
翻动最后一个节点的状态一定会使前面若干节点的状态取反,而答案是异或起来的,所以出现两次就抵消,符合条件。
然后还可以(打表)发现,对于一个点$(x,y)$,它$SG$值就是$lowbit(max(x,y))$
所以,至此,问题转化为每个点的权值为$lowbit(max(x,y))$。求矩阵并的权值异或和。
矩阵面积的套路:扫描线+线段树。坐标过大所以要动态开点。
而$lowbit$这种操作是二进制运算,为了方便我们可以把值域扩大至$[0,2^30)$再建线段树。
这样,线段树的区间都类似与$[aL,(a+1)L-1]$。
这样的区间的$lowbit$异或和:发现对于$lowbit<\frac{L}{2}$的部分,一定两两抵消,所以最终的$lowbit$异或和为$lowbit(l) \ xor \ lowbit(mid)$
然后只要标记永久化表示当前区间被完全覆盖多少次,如果被覆盖则$O(1)$计算否则由儿子上传即可。时间复杂度$O(nlogn)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int S=111111,N=(1<<30)-1; 4 int sum[S<<8],all[S<<8],cnt[S<<8],lc[S<<8],rc[S<<8],pc,ans,R,q,n,m; 5 vector<int>op[S],l[S],r[S]; 6 int cal(int l,int r){return l&-l^r-l+1>>1;} 7 void up(int p){cnt[p]=cnt[lc[p]]^cnt[rc[p]];sum[p]=sum[lc[p]]^sum[rc[p]];} 8 #define md (L+R>>1) 9 void chg(int&p,int l,int r,int v,int L=0,int R=N){ 10 if(!p)p=++pc; 11 if(l<=L&&R<=r){all[p]+=v; if(all[p])cnt[p]=R^L^1,sum[p]=cal(L,R);else up(p); return;} 12 if(l<=md)chg(lc[p],l,r,v,L,md); if(r>md)chg(rc[p],l,r,v,md+1,R); if(!all[p])up(p); 13 } 14 int CNT(int p,int l,int r,int L=0,int R=N){ 15 if(!p)return 0; if(all[p])return min(R,r)^max(L,l)^1; if(l<=L&&R<=r)return cnt[p]; 16 return (l<=md?CNT(lc[p],l,r,L,md):0)^(r>md?CNT(rc[p],l,r,md+1,R):0); 17 } 18 int SUM(int p,int l,int r,int L=0,int R=N,int o=0){ 19 o|=all[p]; if(l<=L&&R<=r)return o?cal(L,R):sum[p]; 20 return (l<=md?SUM(lc[p],l,r,L,md,o):0)^(r>md?SUM(rc[p],l,r,md+1,R,o):0); 21 } 22 int main(){ 23 cin>>q>>n>>m; 24 #define pb push_back 25 for(int i=1,x,y,X,Y;i<=q;++i) 26 scanf("%d%d%d%d",&x,&y,&X,&Y),X++, 27 op[x].pb(1),l[x].pb(y),r[x].pb(Y), 28 op[X].pb(-1),l[X].pb(y),r[X].pb(Y); 29 for(int i=1;i<=n;++i){ 30 for(int j=0;j<l[i].size();++j)chg(R,l[i][j],r[i][j],op[i][j]); 31 ans^=SUM(R,i+1,N)^(CNT(R,0,i)&1)*(i&-i); 32 }cout<<ans<<endl; 33 }
T3:树拓扑序
大意:给定外向树,求所有拓扑序列中,逆序对个数和。$n \le 500$
设$dp[i]$表示$i$的子树的拓扑序逆序对个数,$tp[i]$表示子树拓扑序数,$f[i][j]$表示数字$i$在目前考虑的子树中排名第$j$的方案数。
枚举两棵子树考虑其贡献,那就是,枚举其中一棵树中的某一个权值及其排名,再枚举$bef$它在另一个子树中有多少数在他前面。
$dp[p]+= size(p)! \sum\limits_{a\in son(p)} \ \sum\limits_{b \in son(p),b \neq a} \frac{1}{(size(a)+size(b))!} \prod\limits_{c \in son(p),c \neq a,c \neq b} \frac{tp[c]}{size(c)!} \sum\limits_{val\in subtree(a)} \sum\limits_{rk=1}^{size(a)} f[val][rk] \sum\limits_{bef=0}^{size(b)}\binom{bef+rk-1}{bef} \binom{size(a)-rk+size(b)-bef}{size(b)-bef} (\sum\limits_{VAL \in subtree(b)} \sum\limits_{RK=1}^{size(b)} [(VAL>val \ and \ RK\le bef)or(VAL<val \ and \ RK<bef)] f[VAL][RK] )$
最后一段可以用一个前缀和直接算出来,然后考虑一下前面的复杂度:
看起来是$5$层循环,然而做前缀和时,枚举两个值这里,因为每两个值只会在$lca$处被枚举到一次,再枚举一下排名,一共是$O(n^3)$
然后转移的时候,枚举权值的总复杂度是$O(n)$的,然后接下来枚举的是两个子树的$size$。对于全图求和之后也是$O(n^2)$级别的。
然后更新$dp$也是同理,所以总复杂度是$O(n^3)$的。
转移还有$dp[p]+=dp[son] \times tp[otherson] \times \binom{size(p)-1}{size(son)}$
以及更新$dp$的时候,只需要枚举一个儿子,和当前已经合并出来的整棵树归并。
总时间复杂度$O(n^3)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 505 4 #define mod 1000000007 5 vector<int>v[S],s[S]; 6 int C[S][S],ec,n,sz[S],ans[S],dp[S][S],tp[S],tot[S]; 7 int mo(int a){return a>=mod?a-mod:a;} 8 void dfs(int p){ 9 sz[p]=tp[p]=1; v[p].push_back(p); 10 for(int y:s[p]){ 11 dfs(y); sz[p]+=sz[y]; tp[p]=1ll*tp[p]*tp[y]%mod*C[sz[p]-1][sz[y]]%mod; 12 for(int x:v[y])v[p].push_back(x); 13 } 14 for(int y:s[p])for(int z:s[p])if(z^y){ 15 int c=0,r=1,a=0; 16 for(int w:s[p])if(w!=y&&w!=z)c+=sz[w],r=1ll*tp[w]*r%mod*C[c][sz[w]]%mod; 17 for(int w:v[y]){ 18 for(int i=1;i<=sz[z];++i)tot[i]=0; 19 for(int q:v[z])if(q<w)for(int i=1;i<=sz[z];++i)tot[i]=mo(tot[i]+dp[q][i]); 20 for(int i=1;i<=sz[z];++i)tot[i]=mo(tot[i]+tot[i-1]); 21 for(int i=1;i<=sz[y];++i)for(int j=1;j<=sz[z];++j) 22 a=(a+1ll*dp[w][i]*tot[j]%mod*C[i+j-1][j]%mod*C[sz[y]-i+sz[z]-j][sz[y]-i])%mod; 23 }ans[p]=(ans[p]+1ll*r*a%mod*C[sz[p]-1][c])%mod; 24 }dp[p][1]=tp[p]; 25 for(int y:s[p]){ 26 int c=0,r=1; 27 for(int z:s[p])if(y^z)c+=sz[z],r=1ll*r*tp[z]%mod*C[c][sz[z]]%mod; 28 ans[p]=(ans[p]+1ll*r*ans[y]%mod*C[sz[p]-1][c])%mod; 29 for(int w:v[y]){ 30 for(int i=1;i<=sz[p];++i)tot[i]=0; 31 for(int i=1;i<=sz[y];++i)for(int j=0;j<=c;++j) 32 tot[i+j]=(tot[i+j]+1ll*dp[w][i]*C[i+j-1][j]%mod*C[sz[y]-i+c-j][c-j])%mod; 33 for(int i=1;i<=sz[p];++i)dp[w][i]=1ll*tot[i-1]*r%mod; 34 } 35 } 36 } 37 int main(){ 38 cin>>n; 39 for(int i=1,a,b;i<n;++i)cin>>a>>b,s[a].push_back(b); 40 for(int i=0;i<=n;++i)for(int j=C[i][0]=1;j<=i;++j)C[i][j]=mo(C[i-1][j-1]+C[i-1][j]); 41 dfs(1); 42 for(int i=1;i<=n;++i)for(int j:v[i])if(i<j)ans[1]=mo(ans[1]+tp[1]); 43 cout<<ans[1]; 44 }