sm2025 模拟赛5 (2025.9.13)
文章目录
- T1 方格染色 (easy,100%)
- T2 数字图 (mid+,25%)
- T3 救护车
- T4 打卡(mid+,30%)
T1 方格染色 (easy,100%)
link1/link2
题意
n×nn \times nn×n 的网格图,第一行和第一列的方格已染好色,对于剩下方格 (i,j)(i,j)(i,j) ,col(i,j)=max(col(i,j−1),col(i−1,j))col(i,j)=max(col(i,j-1),col(i-1,j))col(i,j)=max(col(i,j−1),col(i−1,j)) 。求最多被染上的颜色和这种颜色所染的格子数,如有多种颜色则取颜色最大值。
2≤n≤2×105,1≤col≤1092 \le n \le 2 \times 10^5 ,1 \le col \le 10^92≤n≤2×105,1≤col≤109
思路
直接模拟肯定超时,考虑只在相邻两行上有修改的地方才计算贡献,其余的将若干行贡献合在一起算。具体的,将每行中连续一段的相同的 colcolcol 用一个变量储存下来。复杂度均摊 O(n)O(n)O(n) 。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
int a[maxn],b[maxn];
ll sum[maxn*2];
map<int,int>mp;
struct NODE{ ll w,x,y,z; }c[maxn*2];
int main(){freopen("grid.in","r",stdin);freopen("grid.out","w",stdout);int n; scanf("%d",&n); int tot=0;for(int i=1;i<=n;i++){scanf("%d",&a[i]);if(!mp[a[i]]) mp[a[i]]=++tot;sum[mp[a[i]]]++;}for(int i=1;i<=n;i++){scanf("%d",&b[i]);if(!mp[b[i]]) mp[b[i]]=++tot;if(i!=1) sum[mp[b[i]]]++;}int ma=0,cnt=0;for(int i=2;i<=n;i++)if(ma<a[i]) c[++cnt]={a[i],i,0,2},c[cnt-1].y=i-1,ma=a[i];c[cnt].y=n; int l=1;for(int i=2;i<=n;){int l2=l;while(l<=cnt&&c[l].w<=b[i]){sum[mp[c[l].w]]+=(c[l].y-c[l].x+1)*(i-c[l].z);c[l].w=b[i],l++;}if(l2<l){l--;c[l]={b[i],2,c[l].y,i};}int r=i;while(r<=n&&b[r]<=c[l].w) r++;r--;i=r+1;}for(int i=l;i<=cnt;i++)sum[mp[c[i].w]]+=(c[i].y-c[i].x+1)*(n-c[i].z+1);int col=0; ll ans=0;for(int i=1;i<=n;i++){if(sum[mp[a[i]]]>ans) ans=sum[mp[a[i]]],col=a[i];else if(sum[mp[a[i]]]==ans) col=max(col,a[i]);}for(int i=1;i<=n;i++){if(sum[mp[b[i]]]>ans) ans=sum[mp[b[i]]],col=b[i];else if(sum[mp[b[i]]]==ans) col=max(col,b[i]);}printf("%d %lld",col,ans);return 0;
}
T2 数字图 (mid+,25%)
lnk
思路
博弈题。先考虑 部分分1≤ai≤21 \le a_i \le 21≤ai≤2 怎么做。如果 a1=2a_1=2a1=2 ,答案就是 222 ;否则在 111 能一步到达的点中找一个 ai=2a_i=2ai=2 的点,若没有则下一步后手就会直接暂停,答案为 111 ;若有则后手继续做类似移动。若成一个环则答案为 111 ,否则判断是轮到先手还是后手在最后那个出度为 000 的点进行操作。
所以可以知道会从 xxx 走到 yyy 的前提条件为 ax≠aya_x ≠ a_yax=ay ,否则下一步时对手就达到目的了。
设 fi=−1/0/1f_i=-1/0/1fi=−1/0/1 表示到 iii 号点,当前点状态是否确定,若确定那么如果是先手将棋子移到该点则为 111 ,是后手移到该点则为 000 。易知 fi=0f_i=0fi=0 ,可是这样我们不方便确定后续状态,考虑反图上做。我们假设先手最后决定胜负(即先手赢),那么反图上出度为 000 的点的 fff 值为 111 。往回推,若 fi=1f_i=1fi=1 ,则所有与 iii 有边直接相连的点的 fff 值为 000 。这就很像拓扑序了,但是这个图不是 DAGDAGDAG ,且 fff 值为 000 的点不能放进队列中。最后如果 f1=0f_1=0f1=0 ,那么先手胜,即答案为 222 ;否则答案为 111 。
于是我们发现会了图上只有两种点权的做法,即我们可以判断某种点权是否可以被取到,然后发现可以二分点权,于是 O(nlogn)O(nlogn)O(nlogn) 完成。
反思
由于在考场上正着想发现好像有后效性,也没有简化原图,并且不知道化简图后有多条路要选那条走,想的比较人机。所以正难则反。
当然直接模拟多次遍历图的话也有一定概率的正确性,但仅适用于骗分。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=250005,maxm=5e5+5;int idx,head[maxn];
struct EDGE{ int v,next; }e[maxm];
void Insert(int u,int v){e[++idx]={v,head[u]};head[u]=idx;
}int n,m,a[maxn],b[maxn],outd[maxn],vis[maxn];
queue<int>q;
struct EDGE2{ int x,y; }oe[maxm];
bool Check(int mid){idx=0;for(int i=1;i<=n;i++)b[i]=(a[i]>=mid)+1,outd[i]=head[i]=0;if(b[1]==2) return 1;for(int i=1;i<=m;i++)if(b[oe[i].x]!=b[oe[i].y]){outd[oe[i].x]++;Insert(oe[i].y,oe[i].x);}for(int i=1;i<=n;i++)vis[i]=-1;for(int i=1;i<=n;i++)if(!outd[i]){q.push(i);vis[i]=1;//1:先手 0:后手 }while(!q.empty()){int x=q.front(); q.pop();for(int i=head[x];i;i=e[i].next){int v=e[i].v;if(vis[v]==-1){vis[v]=0;for(int j=head[v];j;j=e[j].next){int z=e[j].v;outd[z]--;if(vis[z]==-1&&!outd[z]){vis[z]=1; q.push(z);} }}}}return (!vis[1]);
}int main(){freopen("graph.in","r",stdin);freopen("graph.out","w",stdout);cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=m;i++)cin>>oe[i].x>>oe[i].y;ll l=0,r=1e9+1,mid;while(l+1<r){mid=(l+r)>>1;if(Check(mid)) l=mid;else r=mid;}cout<<l;return 0;
}
T3 救护车
link
T4 打卡(mid+,30%)
link
思路
有点类似 模拟赛2的T3 ,但是这样 dpdpdp 是 O(n×(n−k))O(n\times (n-k))O(n×(n−k)) 的,难以优化转移。
于是直接计算贡献很难,考虑分离贡献。我们会发现走的顺序会形如从 kkk 出发,向左走若干个点然后掉头向右走,再掉头向左走……辟除掉直接从 kkk 走到任意一个点的时间(这部分很好计算),其他都是在到达当前点前所迂回花费的时间。记 fif_ifi 表示从 kkk 走到 iii 的最小额外代价 (i≤k)(i \le k)(i≤k) ,gig_igi 表示从 kkk 走到 iii 的最小额外代价 (i≥k)(i \ge k)(i≥k) 。
有:
fi=min(gj+2×(aj−ai)×(n−j))f_i=min(g_j+2 \times (a_j-a_i)\times(n-j))fi=min(gj+2×(aj−ai)×(n−j))
gi=min(fj+2×(ai−aj)×(j−1))g_i=min(f_j+2 \times (a_i-a_j)\times(j-1))gi=min(fj+2×(ai−aj)×(j−1))
发现这两个式子相互转移,有后效性。但是可以看到 f,gf,gf,g 都具有单调性,也有决策单调性。转移可以类似 DijkstraDijkstraDijkstra 式转移,即哪边答案小就先转移哪边。
但是复杂度仍不对,考虑将式子拆开。
fi=min(gj+2×aj×(n−j)−2×ai×(n−j))f_i=min(g_j+2\times a_j \times(n-j)-2\times a_i \times(n-j))fi=min(gj+2×aj×(n−j)−2×ai×(n−j))
分离 i,ji,ji,j ,可以斜率优化。令 x=ai,y=−2×(n−j),b=gj+2×aj×(n−j)x=a_i,y=-2\times (n-j) ,b=g_j+2\times a_j \times(n-j)x=ai,y=−2×(n−j),b=gj+2×aj×(n−j) ,于是式子就如 fi=kx+bf_i=kx+bfi=kx+b 。套李超线段树就好。
反思
对于无法优化的计算,可以考虑分离不相关的部分,再分别处理,这样就可以减少限制条件,方便进一步思考。