Codeforces Round #749 (Div. 1 + Div. 2, based on Technocup 2022 Elimination Round 1)

A. Windblume Ode
题意是给你一个数组。然后要你求得一个子序列,这个子序列每个数的和是一个合数,并且是该数组能得到的最大的合数。输出这个子序列中每个元素的下标。

一开始看到这个题目的n的要求是大于等于3,又是求合数。想到3个数相加,如果是奇数,那么肯定有一个数是奇数,减去这个奇数就可以得到一个偶数。如果和是偶数,那么一定是合数。
3个数以上也是一样的。所以可以只删去一个数就可以使这个和从素数变成合数。
所以就先求得所有数的和,先判断和是不是合数。如果不是。
然后从小到大遍历一下删去这个数是否是合数即可。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define endl '\n'
const int inf = 0x3f3f3f3f;
const int MAXN = 1e5 + 10;
int n, m, T;
int flag;

struct node {
	int data;
	int set;
};

node a[MAXN];

bool cmp(node x, node y)
{
	return x.data < y.data;
}
int main()
{
	std::ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> T;
	while (T--) {
		cin >> n;
		int sum = 0;
		for (int i = 1; i <= n; i++) {
			cin >> a[i].data;
			sum += a[i].data;
			a[i].set=i;
		}
		flag = 0;
		for (int i = 2; i < sum; i++) {
			if (sum % i==0) {
				flag = 1;
				break;
			}
		}
		// cout<<sum<<endl'
		if (flag) {
			cout << n << endl;
			for (int i = 1; i <= n; i++) {
				cout << i << " ";
			}
			cout << endl;
		} else {
			sort(a + 1, a + 1 + n, cmp);
			for (int i = 1; i <= n; i++) {
				int d = sum - a[i].data;
				for (int j = 2; j < d; j++) {
					if (d%j==0) {
						flag = a[i].set;
						// cout<<flag<<endl;
						break;
					}
				}
				if(flag) break;
			}
			cout << n - 1 << endl;
			for (int i = 1; i <= n; i++) {
				if (i == flag) {
					continue;
				}
				cout << i << " ";
			}
			cout << endl;
		}
	}
	return 0;
}

B. Omkar and Heavenly Tree
题意:给你n个节点和m个约束。让你构造一个满足m个约束的无向图。
一个约束由 a b c 三个数组成,即不能出现 a连接b 然后b连接c的情况。

因为每一个约束,约束的是中间的节点b两头不能是a 或c。
那么我们可以找到一个点,他没有被限制两边的点。我们就可以让所有的点都连接它即可。
同样是观察数据,题目给出的约束是m个,m<n。
所以至少是有一个点是不会被约束的。所以我们找出这个点即可,然后其他所有点都连接他即可。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define endl '\n'
const int inf=0x3f3f3f3f;
const int MAXN=1e5+10;
int n,m,T;
int flag; 

int vis[MAXN];
int main() 
{
	std::ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=m;i++){
			int a,b,c;
			cin>>a>>b>>c;
			vis[b]=1;
		}
		for(int i=1;i<=n;i++){
			if(vis[i]==0){
				for(int j=1;j<=n;j++){
					if(i==j) continue;
					cout<<i<<" "<<j<<endl;
				}
				break;
			}
		}
	}
	return 0;
}

C. Omkar and Determination
题意:给你一个n*m的地图。'X’表示不可移动,‘.’表示可以移动,但是只能向上或者向左移动。并且移动方向上不能有不可以移动的点。
有k次查询,每次查询是一个区间(l,r)。会将这个区间的列单出拿出来,然后得到这个区间每个点是否可以移动出这个范围的图。无论是从上还是从左都可以。你要输出,是否可以根据这个每个点是否可以移动出这个范围的图,得到原来这个区间的可移动和不可移动的点。

所以题目就变成了,找出这个区间是否有点无论是否是可以移动的点,都被限制了不能移动。因为如果存在这样的点,那么你得到的图中,就无法得到这个点是可移动的还是不可移动的,因为无论是哪个他都移动不出这个范围。

满足这样的点的条件是,上面和左边都是不可以移动的点。这样他就移动不了了,
所以就遍历一下得到这些点即可。
因为每次区间查询都是得到区间的每一列。所以你就只要存每列得到多少个这样的点即可。
因为是区间查询,所以用前缀和来查询这个区间有没有这样的点即可。
然后问题就来了,就是第i列的点满足无法确定的条件之一是,这个点左边是不可以移动的点。
所以如果这个点要是无法确定,必须左边的这一列也要在区间里面。
所以求前缀和的时候,是

vis[r-1]-vis[l-1]
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define endl '\n'
const int inf=0x3f3f3f3f;
const int MAXN=1e6+10;
int n,m,T;
int flag; 

char s[2][MAXN];
int vis[MAXN];
int main() 
{
	std::ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	cin>>s[0];
	for(int i=1;i<n;i++){
		cin>>s[1];
		for(int j=1;j<m;j++){
			if(s[0][j]=='X'){
				if(s[1][j-1]=='X'){
					vis[j]++;
				}
			}
		}
		strcpy(s[0],s[1]);
	}
	for(int i=0;i<m;i++){
		vis[i]+=vis[i-1];
	}

	cin>>T;
	while(T--){
		int l,r;
		cin>>l>>r;
		if(l==r){
			cout<<"YES"<<endl;
			continue;
		}
		if(vis[r-1]-vis[l-1]){
			cout<<"NO"<<endl;
		}else{
			cout<<"YES"<<endl;
		}
	}
	return 0;
}

D. Omkar and the Meaning of Life
题意是有一个1到n的序列p。你要用最多2n次查询,得到这个序列p。
每次查询是你输出一个序列a 。然后会得到一个序列 b 。 bi=ai+pi;
然后会返回一个数,这个数是 b序列中出现两次或者两次以上的数的最小的下标。
如果不存在返回0;

因为可以查询2n次,所以其实可以每2次查询找到一个数即可。
一种做法是,每次选一个位置加1,然后其他位置都加2,这样就会使b数组中某个数出现两次,然后就会返回下标。因为,只会返回小的那个下标。如果是返回的是你选的那个位置,那这次查询就没有意义。但如果不是,那么就相当于找到了比这个位置的数大1的数的位置。就可以得到一些数关系。
所以可以再进行一轮查询,选择一个位置加2,然后其他位置都加1,然后返回的下标如果不是你选中的位置,那么就是比你选中的位置小1的数的位置。又可以确定一些数的关系。
因为这两轮查询,一个是找小1的数的位置,一个是找大1的数的位置。所以结合两轮查询,可以找到所有的数的位置对应关系,即比他大1的数的位置。
因为在查询中,如果不存在出现两次或两次以上的数就会返回0,所以nxt[0]存的就是数字1的位置。
因为在某次查询时。找不到比他小1的数的位置,根据

 nxt[m]=i;//m为查询到的位置。

就将其存了下来,然后遍历一遍即可。

还有一种做法就是。以最后一个数为基准
第一轮查询是,最后一个位置每次都加1,然后其他位置的数从2加到n。
这样每次都可以得到一个下标的数和最后一个数的差距,然后这轮查询之后,就可以统计出有多少的数比他小,就可以得到最后一个数是多少,然后再进行一轮查询。每个数都加1,最后一个数从2加到n即可得到剩下的数是多少。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define endl '\n'
const int inf = 0x3f3f3f3f;	
const int MAXN = 1e5 + 10;
int n, m, T;
int flag;

int a[200];
int ans[200];
int nxt[200];

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cout << "? ";
		for (int j = 1; j <= n; j++) {
			if (j == i) {
				cout << "1 ";
			} else {
				cout << "2 ";
			}
		}
		cout << endl;
		fflush(stdout);
		cin >> m;
		if (m != i) {
			nxt[m] = i;
		}
		cout << "? ";
		for (int j = 1; j <= n; j++) {
			if (i == j) {
				cout << "2 ";
			} else {
				cout << "1 ";
			}
		}
		cout << endl;
		cin >> m;
		if(m!=i){
			nxt[i]=m;
		}
		fflush(stdout);
	}
	cout<<"! ";
	int cnt = 0, now = nxt[0];
	while(now) ans[now] = ++cnt, now = nxt[now];
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	cout<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值