『模拟赛』暑假集训CSP提高模拟19

『模拟赛』暑假集训CSP提高模拟19

日常挂分:T2 \(\color{purple} RE\) -76pts

单看T2 T3怕不是学长失恋了(逃

T1 数字三角形

简单贪心。

能往左放就往左放,不行再往下挂。

正确性:

  1. 无论怎么填,一定不会出现某个连通块向右填的情况。

    比如你现在填到第 \(i\) 个数。右边的数要么填满了格子,要么还有剩余。因为格子数是 \(n + (n-1) + \space ··· \space + (n-i+1)\),这正好对应了排列 \(p\) 中前 \(i\) 大的数。因此格子剩余最少的情况(剩0个)就是 \({p_i=n-i+1}\),即 \(p\) 为降序排列。否则一定会有剩余。故一定没有连通块向右填的。

  2. 由上得,每个连通块只会向左或向下拐,而每个块都不会影响下一个块。虽然怪多少次未知,但一定会尽可能填满之前留下的空隙。所以最后每个块都可以连续生成,也就是说本题恒有解。

int n,a[N],strike[N][N];

signed main(){
	n=rd;
	for(int i=1;i<=n;i++){
		a[i]=rd;
		strike[i][i]=a[i];
	}
	
	int cnt;
	for(int i=1;i<=n;i++){
		cnt=a[i]-1;
		int x=i,y=i,num=a[i];
		while(cnt){
			if(strike[x][y-1]==0 && y-1>0){
				strike[x][y-1]=num;
				--cnt;
				--y;
			}else if(strike[x+1][y]==0 && x+1<=n){
				strike[x+1][y]=num;
				--cnt;
				++x;
			}
		}
	}
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			printf("%lld ",strike[i][j]);
		}
		putchar('\n');
	}
	return Elaina;
}

T2 那一天她离我而去

赛时数组开爆力~ (悲

赛时就是常规思路呀,只需要求经过1的最小环,所以只要遍历一遍节点1的所有边删除后跑dijkstra就好了。

放心,用的vector,不会RE。

int T,n,m;
int dis[N];
vector<pair<int,int> > vec[N];
bool flg=0,vis[N],dfsvis[N],vis1[N];
int idx,hd[N];

priority_queue<pair<int,int> > q; 
void dij(int x,int dv){
	for(int i=0;i<=n;i++) dis[i]=inf,vis[i]=0;
	dis[x]=0;
	q.push({0,x});
	while(!q.empty()){
		int k=q.top().second;
		q.pop();
		if(vis[k]) continue;
		vis[k]=1;
		for(auto nd:vec[k]){
			int to=nd.fi;
			if((k==x&&to==dv)||(to==x&&k==dv)) continue;
			if(dis[to]>dis[k]+nd.se){
				dis[to]=dis[k]+nd.se;
				if(!vis[to]){
					q.push({-dis[to],to});
				}
			}
		}
	}
}

signed main(){
//	freopen("leave.in","r",stdin);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	cin>>T;
	while(T--){
		for(int i=0;i<=n;i++){
			vec[i].clear();
		}
		
		cin>>n>>m;
		for(int i=1;i<=m;i++){
			int x,y,z;
			cin>>x>>y>>z;
			vec[x].psb({y,z});
			vec[y].psb({x,z});
		}
		int ans=inf;
		for(auto nd:vec[1]){
			int to=nd.fi;
			dij(1,to);
			ans=min(ans,dis[to]+nd.se);
		}

		printf("%lld\n",ans==inf?-1:ans);
	}
	return Elaina; 
}

正解是二进制分组啊~ 不会啊~

现在会了。

对与节点1相连的点进行二进制分组。

若与 \(1\) 相连的点 \(x\) ,当前枚举到的这一位是 \(1\) ,则保留 边 \((1,x,w)\) ;否则删去 边 \((1,x,w)\),并向炒鸡原点连一条边 \((n+1,x,2)\)。最后将到 \(n+1\) 的最短路与答案取 min 即可。

具体在代码中,

vec 代表除去与 \(1\) 相连的所有边
vec1 是真正跑 Dij 的图
broke 存的是所有与 \(1\) 相连的边

int T,n,m;
int dis[N];
vector<pair<int,int> > vec[N],vec1[N],broke;
bool flg=0,vis[N],dfsvis[N],vis1[N];
int idx,hd[N];

void dij(int x){
	for(int i=0;i<=n+1;i++) dis[i]=inf,vis[i]=0;
	priority_queue<pair<int,int> > q; 
	dis[x]=0;
	q.push({0,x});
	while(!q.empty()){
		int k=q.top().second;
		q.pop();
		if(vis[k]) continue;
		vis[k]=1;
		for(auto nd:vec1[k]){
			int to=nd.fi;
			if(dis[to]>dis[k]+nd.se){
				dis[to]=dis[k]+nd.se;
				if(!vis[to]){
					q.push({-dis[to],to});
				}
			}
		}
	}
}


signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	cin>>T;
	while(T--){
		broke.clear();
		for(int i=0;i<=n+1;i++){
			vec[i].clear();
			vec1[i].clear();
		}
		
		cin>>n>>m;
		for(int i=1;i<=m;i++){
			int x,y,z;
			cin>>x>>y>>z;
			if(x==1){
				broke.psb({y,z});
			}else if(y==1){
				broke.psb({x,z});
			}else{
				vec[x].psb({y,z});
				vec[y].psb({x,z});
			}
		}
		
		int xm=log2(n)+1,ans=inf;
		for(int i=0;i<=xm;i++){
			for(int j=1;j<=n;j++){
				vec1[j]=vec[j];
			}
			for(auto nd:broke){
				if((nd.fi>>i)&1){
					vec1[1].psb({nd.fi,nd.se});
					vec1[nd.fi].psb({1,nd.se});
				}else{
					vec1[n+1].psb({nd.fi,nd.se});
					vec1[nd.fi].psb({n+1,nd.se});
				}
			}
			dij(1);
			if(dis[n+1]!=inf){
				ans=min(ans,dis[n+1]);
			}
		}
		printf("%lld\n",ans==inf?-1:ans);
	}
	return Elaina;
}

T3 哪一天她能重回我身边

T4 单调区间

赛后调的BF过了? 样例似乎有点水。

int n,a[N],k1[N],k2[N];
int ans;

bool check(int l,int r){
	int pos1=r,pos2=r;
	for(int i=r-1;i>=l;i--){
		if(a[i]>a[pos1]) pos1=i;
		if(a[i]<a[pos2]) pos2=i;
		if(pos1!=i&&pos2!=i) return 0;
	}
	return 1;
}

signed main(){
	n=rd;
	for(int i=1;i<=n;i++) a[i]=rd;
	
	int i=1,j=1,lst=1;
	while(1){
		int l=lst,r=n;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(i,mid)){
				l=mid+1;
			}else{
				r=mid-1;
			}
		}
		j=r;
		int mx=0,mn=inf;
		for(int k=i;k<=j;++k){
			if(a[k]<a[r])mx=max(mx,a[k]);
			if(a[k]>a[r])mn=min(mn,a[k]);
		}
		if(!mx)mx=a[r];
		if(mn==inf)mn=a[r];
		++j;
		while(j<=n){
			if(a[j]>mn&&a[j]<mx)break;
			mx=max(a[j],mx);
			mn=min(a[j],mn);
			++j;
		}
		--j;
		lst=r;
		if(j==n){
			ans+=(j-i+2)*(j-i+1)/2;
			return cout<<ans,0;
		}
		ans+=j-i+1;
		++i;
	}
	return Elaina;
}
posted @ 2024-08-12 16:26  Elaina_0  阅读(39)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end