[构造训练]CF1227G Not Same,CF1375H Set Merging,CF1364E X-OR


~~脑子是个好东西,希望人人都有
在这里插入图片描述

构造真的不是个东西,看了一天视频,没有一道题会做~~

T1:CF1227G Not Same

点击查看

solution

通过观察样例输出,像01矩阵,事实证明,的确如此
将问题转化一下,变成矩阵思考
每一列的和一定,每一行互不相同

先考虑如果全是 n n n,可以怎么构造??
——很简单
n × n n\times n n×n的全是 1 1 1的矩阵,挖掉对角线,然后再填一行全是 1 1 1

这个做法提供了正解方向
考虑能否构造出一个 n + 1 n+1 n+1 n n n列的符合要求的矩阵??
——答案是当然可以

将所有数字从大到小排序,对于 i i i,就从第 i i i行开始填
一直往下填,如果不够就跳到第 1 1 1行再继续填
在这里插入图片描述


简单证明一下,为什么这么填就保证了行行之间互不相同??——反证法

假设第 i i i行和第 j j j行相同 ( i < j ) (i<j) (i<j),用 ( i , j ) (i,j) (i,j)表示第 i i i行第 j j j

必有 ( i , j ) = 1 , ( j , i ) = 1 (i,j)=1,(j,i)=1 (i,j)=1,(j,i)=1
j j j行和第 i i i行的 j j j列都为 1 1 1,表明 a j > 1 a_j>1 aj>1

思考 i + 1 i+1 i+1列,有 a i ≥ a i + 1 a_i\ge a_{i+1} aiai+1
因为所有数字取值 [ 1 , n ] [1,n] [1,n],而我们构造的矩阵为 n + 1 n+1 n+1行,所以必有 ( i , i + 1 ) = 0 (i,i+1)=0 (i,i+1)=0
对应过去必有 ( j , i + 1 ) = 0 (j,i+1)=0 (j,i+1)=0

( i , i + 1 ) = 0 (i,i+1)=0 (i,i+1)=0这个条件能说明什么??
—— a i > a i + 1 a_i>a_{i+1} ai>ai+1,即 a i a_i ai一定严格大于 a i + 1 a_{i+1} ai+1

再思考 i + 2 i+2 i+2列,有 a i > a i + 1 ≥ a i + 2 , ( i + 1 , i + 2 ) = 0 a_i>a_{i+1}\ge a_{i+2},(i+1,i+2)=0 ai>ai+1ai+2,(i+1,i+2)=0
可以画画图,发现如果想要 ( i , i + 2 ) = 1 (i,i+2)=1 (i,i+2)=1,必有 a i = a i + 1 a_i=a_{i+1} ai=ai+1,矛盾
所以 ( i , i + 2 ) = 0 (i,i+2)=0 (i,i+2)=0,对应有 ( j , i + 2 ) = 0 (j,i+2)=0 (j,i+2)=0

( i + 1 , i + 2 ) = 0 , ( i , i + 2 ) = 0 (i+1,i+2)=0,(i,i+2)=0 (i+1,i+2)=0,(i,i+2)=0这个条件又能说明什么??
—— a i > a i + 1 > a i + 2 a_i>a_{i+1}>a_{i+2} ai>ai+1>ai+2

以此类推,可以推出 a i > a i + 1 > . . . > a j − 1 a_i>a_{i+1}>...>a_{j-1} ai>ai+1>...>aj1
( j , i + 1 ) = 0 , ( j , i + 2 ) = 0... ( j , j − 1 ) = 0 (j,i+1)=0,(j,i+2)=0...(j,j-1)=0 (j,i+1)=0,(j,i+2)=0...(j,j1)=0
关注 ( j , j − 1 ) = 0 (j,j-1)=0 (j,j1)=0,翻译一下:第 j − 1 j-1 j1列的第 j j j行为 0 0 0
然而根据我们的规则,第 j − 1 j-1 j1列应当从 j − 1 j-1 j1行开始填
于是得到 a j − 1 = 1 a_{j-1}=1 aj1=1,又因为排序是从大到小, a ∈ [ 1 , n ] a∈[1,n] a[1,n],所以 a j − 1 = a j = 1 a_{j-1}=a_j=1 aj1=aj=1
与上面求出的 a j > 1 a_j>1 aj>1矛盾

故证明了构造的不重复性

在这里插入图片描述

在这里插入图片描述

code

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 1005
int n, opt;
int a[maxn], id[maxn], pos[maxn];
int matrix[maxn][maxn];

bool cmp( int i, int j ) {
	return a[i] > a[j];
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &a[i] ), id[i] = i;
	sort( id + 1, id + n + 1, cmp );
	for( int i = 1;i <= n;i ++ )
		pos[id[i]] = i;
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= a[id[i]];j ++ ) {
			int p = ( i + j - 1 ) > n + 1 ? i + j - n - 2 : i + j - 1;
			matrix[p][i] = 1;
		}
	printf( "%d\n", n + 1 );
	for( int i = 1;i <= n + 1;i ++ ) {
		for( int j = 1;j <= n;j ++ )
			printf( "%d", matrix[i][pos[j]] );
		printf( "\n" );
	}
	return 0;
}

T2:CF1364E X-OR

点击查看

solution

首先要了解 ∣ | 操作原理,两个数的二进制上对应位置有一个为 1 1 1,则 ∣ | 的结果便为 1 1 1
故有两个很显然的结论

1. 0 ∣ x = x 0|x=x 0x=x
2. x ∣ y ≥ x , x ∣ y ≥ y x|y\ge x,x|y\ge y xyx,xyy

观察总操作次数比 2 n 2n 2n多了几次常数操作
于是乎,想到如何在 n n n次查询左右找出 0 0 0所在的位置
然后将其余位置依次与 0 0 0询问,就能得到位置上的数值大小了

接下来就只说说如何找 0 0 0??
——其实很简单
先随便选两个 x , y x,y x,y,然后与剩下的数依次查询
1. P x ∣ P y > P y ∣ P z ⇒ P x ≠ 0 , x = z P_x|P_y>P_y|P_z\Rightarrow P_x≠0,x=z PxPy>PyPzPx=0,x=z
2. P x ∣ P y < P y ∣ P z P_x|P_y<P_y|P_z PxPy<PyPz,此时不进行任何操作
3. P x ∣ P y = P y ∣ P z ⇒ P y ≠ 0 , y = z P_x|P_y=P_y|P_z\Rightarrow P_y≠0,y=z PxPy=PyPzPy=0,y=z
这样就锁定了 x , y x,y x,y中必有一个为 0 0 0
再随机 z z z,或 P z P_z Pz的值更小的,便是 0 0 0
在这里插入图片描述

code

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 5000
int n;
int s[maxn], ans[maxn];

int ask( int x, int y ) {
	printf( "? %d %d\n", x, y );
	fflush( stdout );
	int z;
	scanf( "%d", &z );
	return z;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		s[i] = i;
	random_shuffle( s + 1, s + n + 1 );
	int x = s[1], y = s[2];
	int val = ask( x, y ); 
	for( int i = 3;i <= n;i ++ ) {
		int z = s[i];
		if( z == x || z == y ) continue;
		else {
			int w = ask( z, y );
			if( w < val ) val = w, x = z; 
			else if( w == val ) y = z, val = ask( x, y);
		}
	}
	while( 1 ) {
		int z = s[rand() % n + 1];
		if( z == x || z == y ) continue;
		else {
			int v1 = ask( x, z );
			int v2 = ask( y, z );
			if( v1 == v2 ) continue;
			/*
			不能删去!!
			可能出现x,y其中一个为0
			另外一个又恰好是z的子串(其二进制上为1的每一位,z对应的也是1)
			此时或起来的值都是z
			无法判断谁是0 
			*/ 
			if( v1 > v2 ) swap( x, y );
			break;
		}
	}
	for( int i = 1;i <= n;i ++ )
		if( i == x ) continue;
		else ans[i] = ask( i, x );
	printf( "!" );
	for( int i = 1;i <= n;i ++ )
		printf( " %d", ans[i] );
	return 0;
} 
/*
P(x)|P(y)>P(y)|P(z) ——> P(x)≠0 z代替x 
P(x)|P(y)<P(y)|P(z) 不操作
P(x)|P(y)=P(y)|P(z) ——> P(y)≠0 z代替y
最后随机一个z来判断x,y谁是0 
*/

T3:CF1375H Set Merging

点击查看

solution

先从最原始的暴力下手,即找到 [ l , r ] [l,r] [l,r]里的每一个数,然后依次合并起来即可
——这当然不是正解,但告诉我们单次查询的操作次数上限为 n n n
优化暴力
考虑构建权值线段树,每个节点存储节点区间 [ l , r ] [l,r] [l,r]中每个值出现的位置,再从小到大排序
线段树上每个节点最多有 n 2 n^2 n2种不同本质的查询(即查询的 l , r l,r l,r不同)
可以套一个 m a p map map来记录(记忆化),存在即出现过,即有现成的集合使用
在这里插入图片描述

时间复杂度分析详见第一篇

code

#include <map>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
#define maxq 2200005
#define Pair pair < int, int >
struct node {
	vector < int > num;
	map < Pair, int > Hash;
}t[maxn << 2];
Pair vis[maxq];
int n, Q, cnt;
int a[maxn], pos[maxn], ans[maxn];

void build( int now, int l, int r ) {
	for( int i = l;i <= r;i ++ )
		t[now].num.push_back( pos[i] );
	sort( t[now].num.begin(), t[now].num.end() );
	if( l == r ) return;
	int mid = ( l + r ) >> 1;
	build( now << 1, l, mid ), build( now << 1 | 1, mid + 1, r );
}

int query( int now, int l, int r ) {
	int left = lower_bound( t[now].num.begin(), t[now].num.end(), l ) - t[now].num.begin();
	int right = upper_bound( t[now].num.begin(), t[now].num.end(), r ) - t[now].num.begin() - 1;
	if( right < left ) return 0;
	if( left == right ) return t[now].num[left];
	int pos = t[now].Hash[make_pair( left, right )];
	if( pos ) return pos;
	int lson = query( now << 1, l, r ), rson = query( now << 1 | 1, l, r );
	if( ! lson || ! rson ) return t[now].Hash[make_pair( left, right )] = lson | rson;
	vis[++ cnt] = make_pair( lson, rson );
	return t[now].Hash[make_pair( left, right )] = cnt;
}

int main() {
	scanf( "%d %d", &n, &Q );
	cnt = n;
	for( int i = 1;i <= n;i ++ )
		scanf( "%d", &a[i] ), pos[a[i]] = i;	
	build( 1, 1, n );
	for( int i = 1, l, r;i <= Q;i ++ ) {
		scanf( "%d %d", &l, &r );
		ans[i] = query( 1, l, r );
	}
	printf( "%d\n", cnt );
	for( int i = n + 1;i <= cnt;i ++ )
		printf( "%d %d\n", vis[i].first, vis[i].second );
	for( int i = 1;i <= Q;i ++ )
		printf( "%d ", ans[i] );
	return 0;
} 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值