[考试反思]0208省选模拟21:限制
估分35+14+5=54。
一个喜闻乐见的蓝色的零。
撞了$c++11$的关键字$ref$。长见识。
关键是本机因为太慢所以开不开$c++11$,一开就编译好久,所以一直没有发现。。。
通读三道题,没看懂题。再读一遍,啥都不会。然后开始想,然而啥都没有想出来。
上来先看的T2觉得比较简单(?)。然后就开始写。然而少考虑了不少情况,因为数据的特殊性拿到了一些部分分
然后继续想剩下俩题的正解,再然后就没多少时间了(?)。
这时候发现T1是一个非常经典的大小点问题,想的非常麻烦。
最后剩半个多小时的时候匆忙开始写,写完交,CE而不知。
然后看了眼$T3$把能拿的$5$分拿走。
最后觉得不行就去给$T1$写对拍。写了个暴力,这个暴力后来测得能拿到$13$分。
然后开始运行对拍,拍一会错一个,改过来就多对几组。
最后又开始跑对拍,然后我的机子忍受不了对拍了,一阵轰鸣之后我的虚拟机停止了工作。。。
于是乎,暴力和打的所谓正解都没有交上去,然后依然是那个蓝色的零。。。
然而最后$T1$我写的那个的确能拿$35$分。。。没$AC$的原因是数组开销=小了
改题?我也不知道为啥这么顺,这场考试的题看了题解好像就很简单了。
T2并没有写最终的正解,转而写了和考场上思路一样的那个,复杂度也是对的应该不算在水题吧。。。
最终的正解也看懂了但是好像挺恶心的于是懒得写了。。。
我错了我错了现在我两种都写了。。。。
T1:灯
大意:序列每个点有一种颜色,每次操作会使某一种颜色的所有灯改变状态,初始都没开。每次操作后询问序列有几个极长连续亮灯段。$n,m,q \le 10^5$
联通块数=边数-点数。点数直接开数组维护,边数在每次修改时统计。
抽象成图上的问题之后,我们就又可以想起大小点做法了。根据颜色出现次数的多少划分大小点。
维护一个变量表示对于某种大颜色“两侧的所有亮着的小点的数量”。
修改一个小点时,暴力枚举其出现位置的两侧统计贡献,并且更新上述变量。
修改一个大点时,暴力枚举所有大点讨论相互影响更新答案,再根据上述变量更新小点对该大点的影响。
所以只需要再预处理大点之间的影响即可。时间复杂度$O(q \sqrt(n))$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 111111 4 int a[S],cnt[S],al[S],n,m,q,big[345],bc,tot,ed[S],crs[345][345],re[S]; 5 vector<int>v[S]; 6 int main(){ 7 //freopen("2.in","r",stdin);freopen("1.out","w",stdout); 8 scanf("%d%d%d",&n,&m,&q); 9 for(int i=1;i<=n;++i){scanf("%d",&a[i]);if(a[i]==a[i-1])i--,n--;}a[n+1]=0; 10 for(int i=1;i<=n;++i)cnt[a[i]]++,v[a[i]].push_back(i); 11 for(int i=1;i<=m;++i)if(cnt[i]>=300)big[++bc]=i,re[i]=bc; 12 for(int i=2;i<=n;++i)crs[re[a[i-1]]][re[a[i]]]++,crs[re[a[i]]][re[a[i-1]]]++; 13 while(q-->0){ 14 int x;scanf("%d",&x); 15 if(al[x]){ 16 tot-=cnt[x]; 17 if(re[x]){ 18 tot+=ed[x]; 19 for(int i=1;i<=bc;++i)if(al[big[i]])tot+=crs[i][re[x]]; 20 }else for(int i=0;i<v[x].size();++i) 21 tot+=al[a[v[x][i]-1]],ed[a[v[x][i]-1]]--,tot+=al[a[v[x][i]+1]],ed[a[v[x][i]+1]]--; 22 al[x]=0; 23 }else{ 24 tot+=cnt[x];al[x]=1; 25 if(re[x]){ 26 tot-=ed[x]; 27 for(int i=1;i<=bc;++i)if(al[big[i]])tot-=crs[i][re[x]]; 28 }else for(int i=0;i<v[x].size();++i) 29 tot-=al[a[v[x][i]-1]],ed[a[v[x][i]-1]]++,tot-=al[a[v[x][i]+1]],ed[a[v[x][i]+1]]++; 30 } 31 printf("%d\n",tot); 32 } 33 }
T2:十字路口
大意:n个红绿灯,红灯时有倒计时。你在m个时刻观察了所有灯。已知所有灯有一个公共周期,周期内某一个固定时刻红转绿,某个固定时刻绿转红。求周期长。$nm \le 100000$
对于同一盏灯的两个观测时刻$t_1,t_2$,如果我们观察到它的红灯倒计时为$x,y$。设周期为$T$
我们知道$t_1+x \equiv t_2+y (mod \ T)$。因为这两个时刻都是绿灯亮起的时刻在每个周期里只有一次,所以对$T$同余。
据此列式,把关系建成边,点是观察的时刻,边权是红灯倒计时的差值。这样建出的图中,每个环长都是$T$的倍数。
暴力建图,$floyd$找环,取最小环。时间复杂度$O(nm^2+m^3)$。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,ans=666666666,e[333][333];vector<vector<int> >v; 4 int main(){ 5 cin>>m>>n;if(n>300&&m>300)return 0; 6 v.resize(n+1); 7 for(int i=1;i<=n;++i)v[i].resize(m+1); 8 for(int j=1;j<=n;++j)for(int i=1;i<=m;++i)scanf("%d",&v[j][i]); 9 memset(e,0x3f,sizeof e); 10 if(n>m)for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(v[i][j])for(int k=1;k<=m;++k)if(v[i][k]>v[i][j]) 11 e[j][k]=min(e[j][k],v[i][k]-v[i][j]); 12 if(n<=m)for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)if(v[j][i])for(int k=1;k<=n;++k)if(v[k][i]>v[j][i]) 13 e[j][k]=min(e[j][k],v[k][i]-v[j][i]); 14 for(int i=1;i<=m;++i)e[i][i]=0; 15 for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)for(int k=1;k<=m;++k)e[j][k]=min(e[j][k],e[j][i]+e[i][k]); 16 for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)if(i!=j)ans=min(ans,e[i][j]+e[j][i]); 17 printf("%d\n",ans<666666666?ans:-1); 18 }
同理,我们对于某一个特定周期两个灯设它的红灯结束时间是$t_1,t_2$,某一时刻两个灯的红灯倒计时为$x,y$。设周期为$T$
我们知道$t1-x \equiv t2-y (mod \ T)$。因为做差之后就是当前的时刻。
和上面的那种方法同理,时间复杂度是$O(mn^2+n^3)$
如果我们根据$n,m$的大小关系决定运行上面哪个算法,这样复杂度就是$nm \ min(n,m)$了。也即$nm\sqrt(nm)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,ans=666666666,e[333][333];vector<vector<int> >v; 4 int main(){ 5 cin>>m>>n; 6 v.resize(n+1); 7 for(int i=1;i<=n;++i)v[i].resize(m+1); 8 for(int j=1;j<=n;++j)for(int i=1;i<=m;++i)scanf("%d",&v[j][i]); 9 memset(e,0x3f,sizeof e); 10 if(n>m)for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(v[i][j])for(int k=1;k<=m;++k)if(v[i][k]>v[i][j]) 11 e[j][k]=min(e[j][k],v[i][k]-v[i][j]); 12 if(n<=m)for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)if(v[j][i])for(int k=1;k<=n;++k)if(v[k][i]>v[j][i]) 13 e[j][k]=min(e[j][k],v[k][i]-v[j][i]); 14 if(n<=m)swap(n,m); 15 for(int i=1;i<=m;++i)e[i][i]=0; 16 for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)for(int k=1;k<=m;++k)e[j][k]=min(e[j][k],e[j][i]+e[i][k]); 17 for(int i=1;i<=m;++i)for(int j=1;j<=m;++j)if(i!=j)ans=min(ans,e[i][j]+e[j][i]); 18 printf("%d\n",ans<666666666?ans:-1); 19 }
啊貌似忘说正解了。你发现你连的边都是做差的,而这种关系具有传递性。
$i \rightarrow j : w_j-w_i,j\rightarrow k :w_k-w_j$你还会连一个$i \rightarrow k : w_k-w_i$
然而第三条边显然是没有用的。。。于是我们直接按照权值排序之后相邻的点建边就能取代原图
然后$dfs$找到的简单环就是周期了。代码没有写,因为和上个做法一样所以没啥必要。
在大佬们的压迫之下最后又写了一份正解。。。代码长了30B。。。但是只快了300ms。理论复杂度$O(nmlogn)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 666666 4 int n,m,fir[S],ec,l[S],d[S],v[S],to[S],ins[S],al[S],ans,a[S],op;vector<int>V[S]; 5 void link(int a,int b,int w){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=w;} 6 bool cmp(int a,int b){return V[op][a]<V[op][b];} 7 void dfs(int p,int D){ 8 d[p]=D;al[p]=1;ins[p]=1; 9 for(int i=fir[p];i;i=l[i])if(!al[to[i]])dfs(to[i],D+v[i]);else if(ins[to[i]])ans=v[i]+D-d[to[i]]; 10 ins[p]=0; 11 } 12 int main(){ 13 cin>>m>>n; 14 for(int i=1;i<=n;++i)V[i].resize(m+1); 15 for(int j=1;j<=n;++j)for(int i=1;i<=m;++i)scanf("%d",&V[j][i]); 16 for(int i=1;i<=m;++i)a[i]=i; 17 for(op=1;op<=n;++op){ 18 sort(a+1,a+1+m,cmp);unique(a+1,a+1+m,cmp); 19 for(int i=2;i<=m;++i)if(V[op][a[i-1]]==V[op][a[i]])swap(a[i-1],a[i]);else if(V[op][a[i-1]])link(a[i-1],a[i],V[op][a[i]]-V[op][a[i-1]]); 20 }for(int i=1;i<=m;++i)if(!al[i])dfs(i,0); 21 printf("%d\n",ans?ans:-1); 22 }
T3:密室逃脱
大意:i号点与i+1号点连边,i与i+1能相互到达当且仅当i号点上有$a_i$个人不在移动或$i+1$号点上有$b_i$人没在移动。求序列上最多能放多少人让他们到不了1号点。
$n \le 1000$,边权$\le 10000$
神仙dp神仙考场切啊。。。
当人数足够多时我们发现这一大批人可以一起想到哪里就到哪里。我们称他们能到达的区间为一段。
具体而言定义一段是段内所有人可以一起到达段内任意位置,这称之为一段。
设$dp[i][j]$表示$i$号节点所在的段中有$j$个人,此时前$i$个点能放多少人。
玩家们会采取最优决策。所以$dp$转移比较显然。我们从$dp[i][j]$向外扩展。
若$j <a[i]$那么$i$上的人并不能靠自己向右走,所以考虑在$i+1$点上放人,如果放的人不到$b[i]$个那$i$号点的人的确就走不到i+1$了。
也即$dp[i+1][0,1,...,b[i]-1]=dp[i][j]+(0,1,...,b[i]-1)$
而如果你放的人达到了$b[i]$个左边的人就往右边涌了。$dp[i+1][j+b[i]]=dp[i][j]+b[i]$
如果左侧的人数达到了$a[i]$那么它们当中的$a[i]$个人负责按按钮剩下的人就可以往右走了。
$dp[i+1][j-a[i]]=dp[i][j]$
不考虑在右面在放人了,反正都能走过来在左边放也是一样的。
如果$j\geq a[i]+b[i]$那两边就连成一段可以自由来回了。
$dp[i+1][j]=dp[i][j]$。也不考虑多放人因为左边可以走过去。
数组的第二维开多大?这里需要理解深刻一点了不然就会向我一样$WA$。
$20000$。因为对于一个段如果里面的人数超过了2倍最大边权它就一定走得到0好点。而不到2倍就有可能走不到。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int dp[1111][21111],a[1111],b[1111],n,m,ans; 4 int main(){ 5 scanf("%d%d",&n,&m);memset(dp,0x80,sizeof dp); 6 for(int i=1;i<m;++i)dp[1][i]=i;dp[1][0]=0; 7 for(int i=1;i<n;++i)scanf("%d%d",&a[i],&b[i]); 8 for(int i=1;i<n;++i){ 9 int mx=0x80000000; 10 for(int j=0;j<a[i];++j)dp[i+1][j+b[i]]=max(dp[i+1][j+b[i]],dp[i][j]+b[i]),mx=max(mx,dp[i][j]); 11 for(int j=0;j<b[i];++j)dp[i+1][j]=max(dp[i+1][j],mx+j); 12 for(int j=a[i];j<a[i]+b[i];++j)dp[i+1][j-a[i]]=max(dp[i+1][j-a[i]],dp[i][j]); 13 for(int j=a[i]+b[i];j<=20000;++j)dp[i+1][j]=max(dp[i+1][j],dp[i][j]); 14 }for(int i=0;i<=20000;++i)ans=max(ans,dp[n][i]);cout<<ans<<endl; 15 }