[考试反思]0302省选模拟35:报应
难得考的大致能看了一次 。
虽说T1写的不是正解,但是作为时间最短内存最小代码最短的暴力莫名其妙踩了所有正解我也表示很懵逼。
考场上啥也不会,然而感觉第一题好像不是很容易卡满。
于是随手加了一些小剪枝,卡了卡常之类的,然后我也就不知道为啥它居然就$A$了。
可是它就是个妥妥的暴力啊。虽说和正解思路有一点点相似之处,但是还是$O(n^2)$的。
可嫩这一次就把这个寒假的$RP$耗的差不多了。
$T2$看一眼就知道大概是个点分治,然而点分治这种东西太久没有写过了,想复习也一直没有时间。
于是自然拒绝在考场上试探板子,写了个暴力然后卡了半天常。然而这次并无卵用。
$T3$的话是个神仙结论题,一直想证明但一直不会。拿了最低档暴力就走了。
单参数题不打表简直就是一种罪过。虽说我觉得就算打出来好像也看不出来的样子。。。%%%$yzh$就是了
只是迷迷糊糊的感觉它就是费用流跑二分图带权匹配,但是怎么建二分图?不知道。
结果$T3$到了最后题解也没有证明。而$T2$又毒瘤卡常卡到半夜。
在写$T2$的时候发现了以前写的点分治有一点问题,这次一并也研究明白了。
$FFT$能过,暴力卷积也能过,就是$NTT$过不去。我。。。
T1:two
大意:俩树,上来先指定删除一条边然后每一轮删除所有另一棵树上的,两端点分别在上一轮删除的点的两侧的边。输出这个过程。$n \le 200000$
我用$dfs$序遍历儿子找外面的儿子,用$unordered \ set$存儿子,删边后删除减少遍历量,然后就没有了。
谁告诉我它和普通暴力的区别。。?我也想知道它快的原因(方便以后再用啊$O(n^2)$过二十万这种事情谁不想啊是吧)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 222222 4 unordered_set<int>son[2][S]; 5 vector<int>tbd[2]; 6 int n,f[2][S],dfn[2][S],dfr[2][S],idfn[2][S],tim,al[2][S]; 7 void dfs(int o,int p){ 8 dfn[o][p]=++tim;idfn[o][tim]=p; 9 for(auto x:son[o][p])dfs(o,x); dfr[o][p]=tim; 10 } 11 int main(){ 12 scanf("%d",&n); 13 for(int i=2;i<=n;++i)scanf("%d",&f[0][i]),son[0][f[0][i]].insert(i); 14 for(int i=2;i<=n;++i)scanf("%d",&f[1][i]),son[1][f[1][i]].insert(i); 15 dfs(0,1);tim=0;dfs(1,1); al[0][0]=al[1][0]=1; 16 int o=0;int s;scanf("%d",&s);tbd[0].push_back(s+1);al[0][s+1]=1; 17 while(1){ 18 if(tbd[o].empty())return 0; puts(o?"Red":"Blue"); 19 sort(tbd[o].begin(),tbd[o].end()); 20 for(auto x:tbd[o]){ 21 printf("%d ",x-1);al[o][x]=1;son[o][f[o][x]].erase(x); 22 for(int i=dfn[o][x],p;p=idfn[o][i],i<=dfr[o][x];++i){ 23 if(!al[o^1][p]&&(dfn[o][f[o^1][p]]<dfn[o][x]||dfn[o][f[o^1][p]]>dfr[o][x]))tbd[o^1].push_back(p),al[o^1][p]=1; 24 for(auto u:son[o^1][p])if(!al[o^1][u]&&(dfn[o][u]<dfn[o][x]||dfn[o][u]>dfr[o][x]))tbd[o^1].push_back(u),al[o^1][u]=1; 25 } 26 }puts("");tbd[o].clear();o^=1; 27 } 28 }
正解的思路大概是线段树,每条边对应到另一棵树上,相当于是$dfs$序在某个区间内的点向所有区间外的连边都要断掉。
那么大概就是两棵线段树,一个维护小于一个维护大于,加边一共加入了$O(n \ log \ n)$条。
然后对于线段树的节点开一个$vector$,保持内部有序,实际上每次删除的是一个前缀或后缀(以及懒惰删除),暴力来就可以。
大概就是这个意思了。时间复杂度$O(n \ log \ n)$。常数较大。
T2:bracket
大意:树,节点有括号,求每条有向路径是合法括号序列时,若它能拆成至多$a$个子串满足每个子串都是合法括号串则权值为$a$。
求权值为$1,2,...,n$的括号串个数。$n \le 50000$。
括号转为$+1/-1$这是常识了。然后这种所有树上路径都要计入答案的多半也就是点分治了。
发现你的目的就是把两条经过该点,且路径权值和为$0$的路径合并起来。
合法的权值的统计就是:如果当前这个前缀路径和是出发点到该点所遇到的最大/小值,那么就合法,且权值为这个值的出现次数。
两个权值合并的话那就是经典的卷积。但是因为这道题里卷积次数多长度小,所以没有膜法没有预处理没有浮点数的暴力运行效率也不错。
要特判如果路径权值和不为$0$那么两端合并时权值会$+1$。
以及要注意,两条路径一条左半边一条右半边,只有一边包含出发点否则就算重了。
所以总的时间复杂度就是大常数$O(nlog^2n)$的。如果卷积是暴力的话就是小常数$O(n^2logn)$的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 266666 4 #define mod 998244353 5 #define For(i) for(int i=fir[p];i;i=l[i])if(!al[to[i]]&&to[i]!=fa) 6 int fir[S],l[S],sz[S],to[S],v[S],ec,n,tsz,C,Csz,ans[S],MX,MN,fa,R[S]; 7 char s[3];vector<int>pos[S],neg[S];int r1[S],r2[S],rev[S],al[S],m; 8 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 9 void cen(int p,int fa=0){ 10 int mx=0;sz[p]=1; 11 For(i)cen(to[i],p),sz[p]+=sz[to[i]],mx=max(mx,sz[to[i]]); 12 mx=max(mx,tsz-sz[p]); if(mx<Csz)Csz=mx,C=p; 13 } 14 void dfs(int p,int fa,int v1,int v2,int mx,int mn,int mxc,int mnc){ 15 v1+=v[p];v2+=v[p]; 16 if(v1>mx)mx=v1,mxc=0;if(v1==mx)mxc++,pos[mx].push_back(mxc); 17 if(v2<mn)mn=v2,mnc=0;if(v2==mn)mnc++,neg[-mn].push_back(mnc); 18 MX=max(MX,mx);MN=min(MN,mn); 19 For(i)dfs(to[i],p,v1,v2,mx,mn,mxc,mnc); 20 } 21 void cal(int rt){ 22 for(int x=0,cnt;cnt=0,x<=MX&&x<=-MN;++x){ 23 for(auto y:pos[x])cnt=max(cnt,y),r1[y]++; pos[x].clear(); 24 for(auto y:neg[x])cnt=max(cnt,y),r2[y]++; neg[x].clear(); 25 for(int i=0;i<=cnt;++i)for(int j=0;j<=cnt;++j)R[i+j]+=1ll*r1[i]*r2[j]; 26 for(int i=0;i<=cnt*2;++i)ans[i+(x?-1:0)]+=R[i]*rt; 27 for(int i=0;i<=cnt*2;++i)r1[i]=r2[i]=R[i]=0; 28 }for(int x=MX+1;x<=-MN;++x)neg[x].clear(); 29 for(int x=-MN+1;x<=MX;++x)pos[x].clear(); 30 } 31 void DaC(int p){ 32 al[p]=1;int Mx=0,CNT=0,Mn=0,I=0; 33 For(i)MX=MN=0,dfs(to[i],0,v[p],0,v[p]>0,0,v[p]>0,0),cal(-1); 34 if(v[p]>0)pos[1].push_back(1);MX=v[p]>0;MN=0;r2[0]++;For(i)dfs(to[i],0,v[p],0,v[p]>0,0,v[p]>0,0);cal(1); 35 For(i)tsz=Csz=sz[to[i]],cen(to[i]),DaC(C); 36 } 37 int main(){ 38 scanf("%d",&n); 39 for(int i=1,a,b;i<n;++i)scanf("%d%d",&a,&b),link(a,b),link(b,a); 40 for(int i=1;i<=n;++i)scanf("%s",s),v[i]=s[0]=='('?1:-1; 41 tsz=Csz=n;cen(1);DaC(C); 42 scanf("%d",&m);for(int i=0,x;i<m;++i)scanf("%d",&x),printf("%d\n",ans[x]); 43 }
T3:sum
大意:$\{ 1...n\}$选一个子集$gcd$为$1$。最大化选出数的和。$n \le 2 \times 10^5$
首先题目的意思就是每种质因子只在一种数字里出现。
然后扔结论:一个质因子要么单独出现在一个数字里,要么就是一个$\le \sqrt{n}$的和一个$> \sqrt{n}$的出现在一个数里。
网络流跑二分图带权匹配。这题没了。时间复杂度?网络流你说$P$呢?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 6222222 4 #define ll long long 5 int n,np[S],ans,f[S],p[S],pc,v[S],w[S],fir[S],to[S],d[S],q[S],ec=1,al[S],l[S]; 6 int F(int a,ll b){while(b<=n)b*=a;return b/a;} 7 bool bfs(){ 8 for(int i=1;i<=pc;++i)d[i]=-n,al[i]=0;al[0]=0; 9 for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(d[to[i]]<d[q[h]]+w[i]&&v[i]) 10 d[q[++t]=to[i]]=d[q[h]]+w[i]; 11 return d[pc]>0; 12 } 13 void link(int a,int b,int V,int W){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;w[ec]=W;} 14 void con(int a,int b,int W){link(a,b,1,W);link(b,a,0,-W);} 15 int dfs(int p,int f){ 16 if(p==pc)return f;int r=f;al[p]=1; 17 for(int i=fir[p];i;i=l[i])if(!al[to[i]]&&v[i]&&d[to[i]]==d[p]+w[i]){ 18 int x=dfs(to[i],1); 19 if(!x)d[to[i]]=-n; 20 else v[i]--,v[i^1]++,r--,ans+=w[i]; 21 }return f-r; 22 } 23 int main(){ 24 cin>>n;int sq=sqrt(n); 25 for(int i=2;i<=n;++i)if(!np[i]){ 26 ll r=i;while(1ll*r*i<=n)r*=i; 27 ans+=r;p[++pc]=i;f[pc]=r; 28 for(int j=i+i;j<=n;j+=i)np[j]=1; 29 } 30 for(int i=1;p[i]<=sq;++i)for(int j=pc;p[j]>sq;--j)if(F(p[i],p[j])>f[i]+f[j]) 31 con(i,j,F(p[i],p[j])-f[i]-f[j]); 32 for(int i=1;p[i]<=sq;++i)con(0,i,0);++pc; 33 for(int i=pc-1;p[i]>sq;--i)con(i,pc,0); 34 while(bfs())dfs(0,n); 35 cout<<ans+1<<endl; 36 }