[考试反思]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
",tot);
32     }
33 }
View Code

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
",ans<666666666?ans:-1);
18 }
View Code

同理,我们对于某一个特定周期两个灯设它的红灯结束时间是$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)$了。也即$nmsqrt(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
",ans<666666666?ans:-1);
19 }
View Code

啊貌似忘说正解了。你发现你连的边都是做差的,而这种关系具有传递性。

$i ightarrow j : w_j-w_i,j ightarrow k :w_k-w_j$你还会连一个$i ightarrow 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
",ans?ans:-1);
22 }
View Code

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]$

不考虑在右面在放人了,反正都能走过来在左边放也是一样的。

如果$jgeq 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 }
View Code
原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12284149.html