[考试反思]0414省选模拟70:阻塞
并不知道我在干什么。脑子里乱七八糟的。
状态不好。没理由。就认为是菜吧。
其实今天的题很简单。$T3$直接就是一个结论,不会,就挂了。
剩下两题都想到了。$T2$想到正解然后觉得我另一个想法也对于是就把正解扔了。。。
$100min$自信交$T2$。大样例什么的都是垃圾,$5pts$滚蛋。。。
$T1$写的暴力,有几个细节没处理,复杂度虽然不对但是常数非常小,处理完细节能$AC$
(当然考后改正解了)
$T3$我觉得出题人挺无良的,不会算神秘数就一分都不给。除了神秘数以外的全都想到了。
这题不设个部分分真的是丧心病狂。
改题奇顺,心情复杂。
还是要吐槽出题人这个大样例真的是坑。。。三道题都是过了大样例能得零分五分那样。。。
T1:小Y增员操直播群
大意:初始有$n$集合,初始每个里面有一个$0$。然后进行若干次合并,设较小的集合为$A$,较大的集合为$B$.
那么首先$B$中所有元素$+=size_A$然后所有$B_i$向$A_{i \mod A_size}$连边。
最后合并成了一个集合。给出最后所有点之间的连边关系(连边后点编号可能发生了改变)。问合并关系的深度。
$T \le 10,n \le m \le 10^5$
按照题目含义,合并过程中出现过的任意一个集合都是一个连续的整数区间。
所以记忆化搜索并枚举端点,复杂度大约是$O(m^3logm)$。常数非常小可以通过。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 100005 4 map<int,int>d[S],M[S]; vector<int>v[S]; 5 int n,m,ans; 6 int sch(int l,int r){ 7 if(l==r)return 0; 8 int&D=d[l][r],L=r-l+1;if(D)return D; D=n; 9 for(int p=l;r-p>=p-l;++p){ 10 int _=p-l+1; 11 for(int i=p+1;i<=r;++i){ 12 int z=*lower_bound(v[i].begin(),v[i].end(),l); 13 if(z!=(i-l)%_+l)goto F; 14 z=*upper_bound(v[i].begin(),v[i].end(),z); 15 if(z<=p)goto F; 16 }D=min(D,max(sch(l,p),sch(p+1,r))+1); F:; 17 }return D; 18 } 19 int main(){int t;cin>>t;while(t--){ 20 cin>>n>>m;ans=0; 21 for(int i=0;i<n;++i)v[i].clear(),d[i].clear(),M[i].clear(); 22 for(int i=1,a,b;i<=m;++i){ 23 scanf("%d%d",&a,&b); 24 if(a<b)swap(a,b); 25 if(a==b)ans=-1; 26 if(M[a][b])ans=-1; 27 v[a].push_back(b); 28 M[a][b]=1; 29 } 30 for(int i=0;i<n;++i)sort(v[i].begin(),v[i].end()),v[i].push_back(n); 31 if(ans!=-1)ans=sch(0,n-1); 32 G: printf("%d\n",ans==n?-1:ans); 33 }}
然而实际上可以根据区间右端点连向的最靠左的点来推算分裂出的左边的区间的右端点。
这样复杂度就是$O(mlogm)$了。然而没比暴力快多少。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 100005 4 unordered_map<int,int>M[S]; vector<int>v[S]; 5 int n,m,ans; 6 int sch(int l,int r,int z=0){ 7 if(l==r)return 0; 8 if(v[r].size()<=z)return n; 9 int p=r-(v[r][z]-l)-1; 10 if(r-p<p-l+1){if(v[p].size()<=z)return n;else p=v[p][z];} 11 else if(r-p!=p-l+1)return n; 12 for(int i=p+1;i<=r;++i)if(v[i].size()<=z)return n; 13 else if(v[i][z]!=(i-l)%(p-l+1)+l)return n; 14 if(r-p<p-l+1)return n; 15 return max(sch(l,p,z),sch(p+1,r,z+1))+1; 16 } 17 int main(){int t;cin>>t;while(t--){ 18 cin>>n>>m;ans=0; 19 for(int i=0;i<n;++i)v[i].clear(),M[i].clear(); 20 for(int i=1,a,b;i<=m;++i){ 21 scanf("%d%d",&a,&b); 22 if(a<b)swap(a,b); 23 if(a==b||M[a][b])ans=-1; 24 v[a].push_back(b); 25 M[a][b]=1; 26 } 27 for(int i=0;i<n;++i)sort(v[i].begin(),v[i].end()); 28 if(ans!=-1)ans=sch(0,n-1); 29 printf("%d\n",ans>=n?-1:ans); 30 }}
T2:小J真爱粉交流群
大意:博弈,先手选定第一行的某一个格,后手每回合可以选择若干$(x,y)$使得从$(x,y)$不能直接走到$(x+1,y)$。先手可以向上下左右四个方向走。
走到最后一行游戏结束,得分为经过的所有点权值和(经过多次算多次贡献)。先手要最小化得分。求最终得分。$T \le 50,m,n \le 100$
可以猜到一些结论:存在一种最优方案,每次后手都只会在先手的脚下修墙。最后修出的墙一定是一段区间。当脚下没有墙那么先手就会直接向下走。
故设$f(i,j)$表示从$(i,j)$出发的答案,$fl(i,l,r)$表示当前在第$i$行,$[l,r]$修好了墙,此时先手在$(i,l-1)$。$fr$同理。
时间复杂度$O(Tnm)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 1000000007 4 int pre[103][103],L[103][103][103],R[103][103][103],dp[103][103],n,m,ans; 5 int sum(int x,int l,int r){return pre[x][r]-pre[x][l-1];} 6 int f(int,int); 7 int fl(int,int,int); 8 int fr(int,int,int); 9 int f(int x,int y){ 10 if(x==n)return sum(x,y,y); 11 int&a=dp[x][y]; if(a!=-1)return a; 12 return a=sum(x,y,y)+max(f(x+1,y),min(y==1?inf:fl(x,y,y)+sum(x,y-1,y-1),y==m?inf:fr(x,y,y)+sum(x,y+1,y+1))); 13 } 14 int fl(int x,int l,int r){ 15 if(r-l+1==m-1)return f(x+1,l-1); 16 int&a=L[x][l][r]; if(a!=-1)return a; 17 return a=max(f(x+1,l-1),min(l==2?inf:fl(x,l-1,r)+sum(x,l-2,l-2),r==m?inf:fr(x,l-1,r)+sum(x,l,r+1))); 18 } 19 int fr(int x,int l,int r){ 20 if(r-l+1==m-1)return f(x+1,r+1); 21 int&a=R[x][l][r]; if(a!=-1)return a; 22 return a=max(f(x+1,r+1),min(l==1?inf:fl(x,l,r+1)+sum(x,l-1,r),r==m-1?inf:fr(x,l,r+1)+sum(x,r+2,r+2))); 23 } 24 int main(){int t;cin>>t;while(t--){ 25 scanf("%d%d",&n,&m); 26 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&pre[i][j]),pre[i][j]+=pre[i][j-1]; 27 memset(dp,0xff,sizeof dp); memset(L,0xff,sizeof L); memset(R,0xff,sizeof R); 28 ans=inf; 29 for(int i=1;i<=m;++i)ans=min(ans,f(1,i)); 30 cout<<ans<<endl; 31 }}
T3:青青草原播种计划
大意:维护$n$个集合支持:插入,删除,合并,查询历史版本$mex$以及神秘数。强制在线。$n,q,w_i \le 5 \times 10^5$
(神秘数:用集合中的数做$01$背包,拼不出来的最小值)
不会就爆零的结论:神秘数的求法是初值为$1$,从小到大遍历集合中的数,如果小于等于当前神秘数则神秘数加上当前值。
肉眼可见的可持久化线段树。然而并不用!
发现只要每次修改后记录答案就行。
一个权值线段树,写个线段树合并,维护全局$mex$以及神秘数。
$mex$很好维护,线段树二分也可以。
神秘数的话可以初值为$1$,每次查询树上$\le$当前神秘数的所有数的和$+1$作为新的神秘数。显然最多只有$log$轮。
就没了。不持久化的话代码好写很多,但是由于数据里修改多操作少,所以常数变大了。(可能只是我写丑了
$O(qlog^2n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 500005 4 #define pb push_back 5 #define md (L+R>>1) 6 vector<int>ans1[S],tim[S]; vector<long long>ans2[S]; 7 int n,q,rt[S],pc,lc[S<<6],rc[S<<6],la=-1;long long w[S<<6];bool A[S<<6]; 8 void mdf(int&p,int v,int o,int L=1,int R=S){ 9 if(!p)p=++pc; 10 if(L==R){w[p]=max(0ll,w[p]+o);A[p]=w[p]?1:0;return;} 11 if(v<=md)mdf(lc[p],v,o,L,md);else mdf(rc[p],v,o,md+1,R); 12 A[p]=A[lc[p]]&A[rc[p]]; w[p]=w[lc[p]]+w[rc[p]]; 13 } 14 void merge(int&p1,int p2,int L=1,int R=S){ 15 if(!p2||p1==p2)return; if(!p1){p1=p2;return;} 16 if(L==R){w[p1]+=w[p2];A[p1]=w[p1]?1:0;return;} 17 merge(lc[p1],lc[p2],L,md); merge(rc[p1],rc[p2],md+1,R); 18 w[p1]=w[lc[p1]]+w[rc[p1]]; A[p1]=A[lc[p1]]&A[rc[p1]]; 19 } 20 int mex(int p,int L=1,int R=S){return L==R||!p?L:(A[lc[p]]?mex(rc[p],md+1,R):mex(lc[p],L,md));} 21 long long sum(int p,int r,int L=1,int R=S){ 22 if(R<=r||!p)return w[p]; 23 return sum(lc[p],r,L,md)+(r>md?sum(rc[p],r,md+1,R):0); 24 } 25 void upd(int T,int x){ 26 tim[x].pb(T); 27 ans1[x].pb(mex(rt[x])); 28 long long sm=1,lsm=0; 29 while(lsm!=sm)sm=sum(rt[x],min(S+0ll,lsm=sm))+1; 30 ans2[x].pb(sm); 31 } 32 void dfs(int p,int L=1,int R=S){ 33 if(!p)return; 34 if(L==R){while(w[p])printf("%d ",L),w[p]-=L;} 35 dfs(lc[p],L,md); dfs(rc[p],md+1,R); 36 } 37 int main(){ 38 cin>>n>>q; 39 for(int i=1;i<=n;++i)upd(0,i); 40 for(int T=1,op,x,y;T<=q;++T){ 41 scanf("%d%d",&op,&x);x=(x+la)%n+1; if(op!=4)scanf("%d",&y);else y=T; 42 if(op<=2)mdf(rt[x],y,op==1?y:-y),upd(T,x); 43 if(op==3){y=(y+la)%n+1;if(x!=y)merge(rt[x],rt[y]),rt[y]=0,upd(T,x),upd(T,y);} 44 if(op>=4)y=lower_bound(tim[x].begin(),tim[x].end(),y)-tim[x].begin()-1,printf("%d %lld\n",la=ans1[x][y],ans2[x][y]),la--; 45 }for(int i=1;i<=n;++i)dfs(rt[i]),puts("0"); 46 }