[机房测试]11.5

[机房测试]11.5

各种被卡。。。不过ssw02状态倒是回来了

欢迎转载ssw02的博客:https://www.cnblogs.com/ssw02/p/11799831.html


a

数学题。

30分做法:1e6的大小范围,DP即可。(不判越界20分)

满分做法:枚举 b 的n次方,把原题转化为 T = b^n S + ma 的形式,这样 n 就有只有 log 种取值枚举即可。然后把 m 转化为一个 b 进制的数,贪心选取 ∑xi 即可。 总次数即为 min { n + ∑xi }

b

输入给出链长 N , 和 M 个点对( x,y ) , 询问最长距离的最短值。

先讲一下60分(可以卡到70,吸氧可以过),最坏复杂度 NM2logN

答案具有二分性,转化为判定性问题。

考虑到每次二分的答案lim , 把所有区间长 > lim 的区间拿出来 , 在N上枚举固定左端点L , 那么对于每一个区间, R可以所在的有效区间必定是一段( i > mid 就是固定右端点 ,反正是固定一个点 ) 。 这些有效区间必须有一个公共交点,否则这个点位不合法。如果所有点位都不合法,返回false , 否则返回 true 。

考虑到优化一下, 枚举的点位可以预处理出来,只有一部分点位可以提供来check , 这个优化可以使复杂度将至一个随机数据下只用跑 2s 的时间消耗,吸吸氧再加点杂优化就可以水过( ltw自带小常数,也过了 )

70代码:

#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 100005 ;
inline int read(){
	int s = 0 ; char g=getchar() ; while( g>'9'||g<'0' )g=getchar() ; 
	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ; return s ;
}
int N , M ; 
struct ap{
	int l , r , len ;
}t[MAXN],p[MAXN];
inline bool cmp( ap x , ap y ){
	return x.len > y.len ; 
}
inline bool check( int lim ){
	int now = 0 ; 
	while( now < N ){
		if( t[now+1].len <= lim )break ;
	    else now++ ;
	}
	if( now == 0 || now == 1 )return true ; 
	int L = 1 , R = N  ; 
	for( register int i = 1 ; i <= now ; ++i ){
		if( t[i].l-lim > R || t[i].r+lim < L )return false ;
		L = max( L , t[i].l-lim ) , R = min( R , t[i].r+lim ) ; 
	}
	if( L > R )return false ;
	for( register int i = L ; i <= R ; ++i ){//枚举点位 
	    int flag = true ; int  LL = 1 , RR = N ;
		for( register int j = 1 ; j <= now ; ++j ){//枚举限制 
			int mid = ( t[j].l+t[j].r )>>1 ; 
			if( i <= mid ){
				int k = lim - abs( t[j].l - i ) ; if( k < 0 ){flag = false ; break ; } //这个点位不合法 
				int xl = t[j].r-k  , xr = t[j].r + k  ; 
				if( xl > RR || xr < LL ){flag = false ; break ;}
				LL = max( LL , xl ) , RR = min( RR , xr ) ; 
			}
			else{
				int k = lim - abs( t[j].r - i ) ; if( k < 0 ){flag = false ; break ; } //这个点位不合法 
				int xl = t[j].l-k , xr = t[j].l +k  ; 
				if( xl > RR || xr < LL ){flag = false ; break ;}
				LL = max( LL , xl ) , RR = min( RR , xr ) ; 
			}
		}
		if( flag ){
		    //cout<<i<<endl ;
			return true; 
		}
	}
	return false ;
}
void  dx( int l , int r ){
	int ans = t[1].len ;
	while( l <= r ){
		int mid = (l+r)>>1 ; 
		if( check(mid) )r=mid-1,ans=mid ; 
		else l = mid+1 ; 
	} 
	cout<<ans ; 
}
int main(){
	freopen("b.in","r",stdin) ; 
	freopen("b.out","w",stdout) ;
	N = read() , M = read() ; 
	if( M <= 1 ){cout<<0;return 0;}
	for( int i = 1 ; i <= M ; ++i )
	    t[i].l=read(),t[i].r=read(),t[i].len = t[i].r-t[i].l ; 
	sort( t+1 , t+M+1 , cmp ) ;
	dx( 1 , 100005 ) ;
	return 0 ;  
}

正解:这是一道很巧妙的曼哈顿距离题目 。

经典套路!!!!

在二分答案lim后,我们建立一个以 L 为 x 轴,R 为 y 轴的平面直角坐标系 , 对于任意一个点对 ( x , y ) , 点对 ( u , v )到这个点对的满足建立0边可到达的意义就是坐标系上曼哈顿距离 <= lim ,所有曼哈顿距离小于等于 lim 的点对是一个斜45度的正方形 。

问题转化为多个矩形判断是否同时存在一个交点 。 用线性规划知识将其旋转45度即可 。(pair<>还可以这样用?)

#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 100005 ; 
inline int read(){
	int s=0 ; char g=getchar() ; while( g>'9'||g<'0' )g=getchar() ; 
	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ; return s ; 
}
int N , M , l[MAXN] , r[MAXN] ;
pair<int,int>calc( pair<int,int> a ){
	return make_pair( a.first-a.second , a.first+a.second ) ;
}
inline bool check( int lim ){
	int xl = -2*N, yl = -2*N , xr = 2*N , yr = 2*N ; 
	for( int i = 1 ; i <= M ; ++i ){
		if( r[i]-l[i] <= lim )continue ;
		pair<int,int>L,R ;//左下角和右上角的点 
		L = calc( make_pair( l[i]-lim,r[i] ) ) ; 
		R = calc( make_pair( l[i]+lim,r[i] ) ) ;
		xl = max( xl , L.first ) , xr = min( xr , R.first ) ;
		yl = max( yl , L.second ) , yr = min( yr , R.second ) ; 
		if( xl > xr || yl > yr )return false ; 
	}
	return true ;
}
void  dx( int l , int r ){
	int ans = N ; 
	while( l <= r ){
		int mid = (l+r)>>1 ; 
		if( check(mid) )ans = mid , r = mid-1;
		else l = mid+1 ; 
	}
	cout<<ans ;
} 
int main(){
	freopen("b.in","r",stdin) ;
	freopen("b.out","w",stdout ) ;
	N = read() , M = read() ; 
	for( int i = 1 ; i <= M ; ++i )l[i] = read() , r[i] = read() ; 
	if( M <= 1 ){puts("1");return 0;}
	dx( 1 , N ) ; 
	return 0 ; 
} 

c

IOI2015D2T2弱化版

给出 N 个整数表示 0-N-1 的排列。

再给出 2N 轮小B的操作。

我们考虑到,如果固定了游戏的次数 , 即小B先玩 k 轮再让小 A 连续玩K轮的结果是一样的。

这样考虑问题就变简单了。假设已知 k 轮游戏后可以结束,那么问题就变成了先让小B按指定顺序游戏, 小A 在连续游戏K轮使得变化后的序列转化为一个升序序列 。

这有用到一个经典模型 ,把每个位置看做一个点 , 向 val[i] 连边 , 最小交换次数就是 N-环的数量 。

考虑到小A可以在一回合啥都不做,所以答案具有单调性,二分即可。

代码

#include<bits/stdc++.h>
using namespace std ; 
const int MAXN = 200005 ;
inline int read(){
	int s=0 ; char g=getchar() ; while(g>'9'||g<'0')g=getchar() ; 
	while( g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ;return s; 
}
int N , x[MAXN*2] , y[MAXN*2] , a[MAXN] , b[MAXN] ;
bool vis[MAXN] ;
inline bool check( int lim ){
	for( register int i = 0 ; i < N ; ++i )b[i] = a[i] , vis[i] = false ;
	for( register int i = 0 ; i < lim ; ++i )
	   swap( b[x[i]] , b[y[i]] ) ; 
	int tot = 0 ; 
	for( int i = 0 ; i < N ; ++i ){
		if( vis[i] )continue ; 
		vis[i] = true ; 
		int now = i ; 
		while( !vis[b[now]] ){
			tot++ ; 
			now = b[now] ;
			vis[now] = true ;  
		}
	} 
	return ( tot <= lim )?1:0 ;
}
void  dx( int l , int r ){ 
	int ans = 2*N ; 
	while( l <= r ){
		int mid = (l+r)>>1 ; 
		if( check(mid) )ans = mid , r = mid - 1 ;
		else l = mid + 1 ;
	}
	cout<<ans ; 
}
int main(){
	freopen("c.in","r",stdin) ; 
	freopen("c.out","w",stdout) ;
	N = read() ; int flag = 1 ;
	for( int i = 0 ; i < N ; ++i ){
		a[i] = read() ; if(a[i]!=i)flag = 0 ;
	} 
	for( int i = 0 ; i < 2*N ; ++i )x[i] = read() , y[i] = read() ; 
	if( flag ){cout<<"0" ; return 0 ;}
	dx( 1 , 2*N ) ; //这个二分会模糊1,0边界问题,特判下
	return 0 ;
}

总结

状态还可以。不过考场忘记exgcd的最小整数解写法废了不少时间。

T1 时间浪费在寻找时间复杂度的问题上(事实证明我最后T了一个点。。),还有exgcd。。。

T2的验证很及时,不然60分就没有了。今天的开题顺序还可以斟酌一下。这种经典的模型如果没见过,实际上也不需要花太多时间在一个暴力程序上面卡常,还不如好好检查。

T3 没有打算开正解很吃亏,实际上ssw02要是一来就开正解的话很容易想到交换问题 ,有此想到二分也会很快 。 但是对于经典的交换K次转化为升序问题ssw02确实没写过,这个要好好总结一下,很妙。

posted @ 2019-11-05 17:17  蓝银杏-SSW  阅读(132)  评论(0编辑  收藏  举报
//结束