GXNU集训队第三次训练题解

A. Gregor and Cryptography

应队给爱学数学的阿周也出了一道数学题。
现在有一个质数 P P P ( 5 < = P < = 1 0 9 ) (5 <= P <= 10^9) (5<=P<=109)​,应队发现有两个整数 a , b a,b a,b ( 2 < = a , b < = P ) (2 <= a , b <= P) (2<=a,b<=P), 满足 P P P m o d mod mod a a a = = = P P P m o d mod mod b b b,并且 a a a b b b 满足 2 < = a , b < = p 2 <= a , b <= p 2<=a,b<=p。现在应队希望阿周能找到这两个数。
可是这对阿周来说有点为难,聪明的你能帮阿周解决这个数学问题吗?

分析: 因为 P P P 是质数, 所以一定不是偶数所以 模 2 2 2 一定为 1 1 1, 所以需要找到 P P P m o d mod mod b = 1 b = 1 b=1 的解, 即 b = P − 1 b = P - 1 b=P1

#include<bits/stdc++.h>
#define x first
#define y seconde
#define int long long 
#define gg exit(0);
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define db printf("debug\n");
const int N = 5e5 + 10;
using namespace std;
typedef pair<int, int>PII;
int n, T;
void solve()
{
	cin >> n;

	cout << 2 << " " << n - 1 << '\n';
}
signed main()
{
	io;
	//T = 1;
	cin >> T;
	while(T -- )
	solve();
}


B. Gregor and the Pawn Game

有一个大小为 n n n × n n n 的棋盘。从上往下第i行和从左往下第j列的正方形被标记为 ( i , j ) (i,j) (i,j)。目前,格里高尔在第 n n n 排有一些兵。在第一行也有敌人的兵。在一个回合中,格里高尔移动了他的一个兵。如果目标方块中没有卒子,卒子可以向上移动一个方块(从 ( i , j ) (i,j) (i,j) ( i − 1 , j ) ) (i−1,j)) (i1,j))。此外,当且仅当方块中有敌人兵时,兵可以沿对角线移动一个方块 ( ( ( ( i , j ) (i,j) (i,j) ( i − 1 , j − 1 ) (i−1,j−1) (i1,j1) ( i − 1 , j + 1 ) ) (i−1,j+1)) (i1,j+1))。敌人的兵也被移走。格里高尔想知道他能到达第一行的最大兵数是多少?注意,在这个游戏中只有格里高尔会轮流出场,而敌人的兵永远不会移动。同样,当格里高尔的兵到达第一行时,它被卡住了,不能再移动了。

模拟即可, 看正上方是否有敌方兵, 有的话,判断左上方是否有兵, 再判断右上方是否有兵, 然后要记录一下某个位置已经有自己的兵到达了或者将这个值更改一下,防止重复使用, 即可

#include<bits/stdc++.h>
#define x first
#define y seconde
#define int long long 
#define gg exit(0);
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define db printf("debug\n");
const int N = 5e5 + 10;
using namespace std;
typedef pair<int, int>PII;
int n, T;
char a[N], b[N];
void solve()
{
	cin >> n;
    scanf("%s", a + 1);
    scanf("%s", b + 1);
		int res = 0;
	for(int i = 1; i <= n; i ++ )
	    if(b[i] == '1')
	    {
	    //注意改值,防止重复走
		if(a[i] == '0') res ++, a[i] = '2'; //正上方
		else 
		{
			if(a[i - 1] == '1' )  //左上
				res ++, a[i - 1] = '2'; 
			else if(a[i + 1] == '1')  //右上
				a[i + 1] = '2', res ++;
		}
	    }

	cout <<res << '\n';
}
signed main()
{
	//T = 1;
	cin >> T;
	while(T -- )
	solve();
}


D. Integers Have Friends

C e r s e i L a n n i s t e r , A G a m e o f T h r o n e s b y G e o r g e R . R . M a r t i n Cersei Lannister, A Game of Thrones by George R. R. Martin CerseiLannister,AGameofThronesbyGeorgeR.R.Martin n n n 个节点,从 1 1 1 n n n 编号,每一个点都有一个权值,这些点之间有 m m m 条关系,关系是相互的
一个节点如果和其他节点有关系且和该节点相连的所有点权值都比此节点大的话,这个节点就是脆弱的
现在凡凡想知道如果节点之间打起架来,最后会剩余几个节点,所有脆弱的节点会被杀死,接下来会有 q q q 次操作

  1. u u u v v v 之间增加一条关系
  2. 移除 u u u v v v 的关系
  3. 告诉凡凡如果此时打起架来,最后剩余几个节点

刚开始所有的节点都是存活的

分析: 首先如果每次查询都遍历一次每个节点肯定是会超时的, 我们再看看题, 该节点脆弱    ⟺    \iff 没有节点比它大, 所以我们定义一个数组 d d d, d [ i ] d[i] d[i] 表示比 i i i 节点大的结点的数量

#include<bits/stdc++.h>
#define x first
#define y seconde
#define gg exit(0);
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define db printf("debug\n");
const int N = 5e5 + 10;
using namespace std;
typedef pair<int, int>PII;
int n, T, m;
int d[N];
void solve()
{
	cin >> n >> m;

	for(int i = 1; i <= m; i ++ )
	{
		int a, b;
		cin >> a >> b;
		if(a > b ) swap(a, b);
		d[a] ++;
	}	


	int q;
	cin >> q;
	int sum = 0;
	//没有比他大的
	for(int i = 1; i <= n; i ++ ) if(d[i] == 0) sum ++;
	for(int i = 1; i <= q; i ++ )
	{
		int op, a, b;
		cin >> op;
		if(op == 3) cout << sum << '\n';  //查询
		else 
		{
			cin >> a >> b;
			if(op == 1) 
			{
				if(a > b) swap(a, b);  //a是小结点, b是大结点
				d[a] ++;  // 存在结点b比a结点大
				if(d[a] == 1) sum --;  //有结点比它大, 脆弱数减少
			}
			else 
			{
				if(a > b) swap(a, b);  
				d[a] --;  //删除边, 所以比a 大的点减少
				if(d[a] == 0 ) sum ++; 
			}

		}
	}
}
signed main()
{
    io;
	T = 1;
	while(T -- )
	solve();
}


D. Integers Have Friends

给出一个数组,求这个数组中一段连续区间使得这个区间中所有数字除以一个数字余数相同,使这个区间尽可能大,输出区间长度

要满足 a l a_l al m o d mod mod m m m, a l + 1 a_{l + 1} al+1 m o d mod mod m m m,… a r a_r ar m o d mod mod m m m 相同由同余的性质可以知道 | a l + 1 − a l a_{l + 1} - a_l al+1al | m o d mod mod m m m = | a l + 2 − a l + 1 a_{l + 2} - a_{l + 1} al+2al+1 | m o d mod mod m m m = | a l + 3 − a l + 2 a_{l + 3} - a_{l + 2} al+3al+2 | m o d mod mod m m m … = | a r − a r − 1 a_r - a_{r - 1} arar1| m o d mod mod m m m

不难发现我们由原数组变成了差分数组, 所以要使得一段区间内的取模相同, 则不难想到求区间内的最大公约数, 且最大公约数大于 1 1 1, 这样他们模上他们的 G C D GCD GCD, 一定是相同的, 所以问题转化成求区间内差分数组的 G C D GCD GCD, 对于区间问题, 这里有两种做法, 第一种是用线段树求区间 G C D GCD GCD, y总的提高课是有讲过的, 第二种是用 S T ST ST 表求 G C D GCD GCD

S T ST ST 表做法:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 2e5 + 10, M = 30;

int st[N][M];
int w[N], a[N];
int n;

void init()  
{
	for (int j = 0; j < M; j ++ )
        for (int i = 1; i + (1 << j) - 1 < n; i ++ )
            if (!j) st[i][j] = w[i];
            else st[i][j] = __gcd(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r)
{
	int len = r - l + 1;

	int k = log(len) / log(2);
	int num = __gcd(st[l][k], st[r - (1 << k) + 1][k]);
	// cout << num << '\n';
	return num;
}

void solve()
{
	cin >> n;
	cin >> a[0];  //注意这里第一个差分是不需要的, 我们只需要两个相邻数的差即可
	//w[1] = a[1] - a[0], w[2] = a[2] - a[1]...
	for(int i = 1; i < n; i ++ ) cin >> a[i], w[i] = abs(a[i] - a[i - 1]);

	if(n == 1)  //特判
	{
	 	cout << 1 << '\n';
	 	return;
	}	

	init();  //预处理

	int ans = 0;
	for(int i = 1; i + ans < n; i ++ )  //双指针
	{

		while(i + ans < n && query(i, i + ans) > 1) ans ++;
	}
	cout << ans + 1 << '\n'; //+1是因为只有一个数的时候长度为1
	
}
signed main()
{
    
	int t;
	cin >> t;
	while(t -- ) solve();
}

线段树写法:

#include<bits/stdc++.h>

using namespace std;
#define int long long 

const int N = 4e5 + 10;
int a[N], w[N];
struct node
{
    int l, r;
    int  gcd;
}tr[N * 4];



void pushup(int u)
{
    tr[u].gcd = __gcd(tr[u << 1].gcd, tr[u << 1 | 1].gcd);
}

void build(int u, int l, int r)  //建树
{
	tr[u] = { l, r, w[l]};
	if (l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1,  mid + 1, r);
	pushup(u);
}
int  query(int u, int l, int r) //查询
{
	if (l <= tr[u].l && r >= tr[u].r) return tr[u].gcd;
	int mid = tr[u].l + tr[u].r >> 1;
	int  res = 0;
	if (l <= mid) res = query(u << 1,l, r);
	if (r > mid) res = __gcd(res, query(u <<1 | 1, l, r));
	return res;
}

void solve()
{
    int n;
    cin >> n;
	for(int i = 1; i <= n; i ++ ) 
	cin >> a[i];

		if (n == 1) 
		{
		    printf("1\n");
	    	return;
		}

		for (int i = 2; i <= n; ++ i)  //处理
			w[i] = abs(a[i] - a[i - 1]);
	  	build(1, 2, n);
         int ans = 0;
    
    for(int i = 2; i <= n; i ++ )
        while(i + ans <= n && query(1, i, i + ans) > 1) ans ++;
    cout << ans + 1 << '\n';
}
signed main()
{
    int t;
    cin >> t;
    while(t -- ) solve();
}

C. Mikasa

题意: 给定两个整数n和m,求出序列 n ⊕ 0 , n ⊕ 1 , … , n ⊕ m n⊕0,n⊕1,…,n⊕m n0,n1nm M E X MEX MEX。这里, ⊕ ⊕ 是按位异或运算符。非负整数序列的 M E X MEX MEX 是这个序列中没有出现的最小的非负整数。例如, M E X ( 0 , 1 , 2 , 4 ) = 3 , M E X ( 1 , 2021 ) = 0 。 MEX(0,1,2,4)=3, MEX(1,2021)=0。 MEX(0,1,2,4)=3,MEX(1,2021)=0

首先对于位运算的题目, 我们要把每一位拆开来看, 首先先转化一下题意, 要求的是没有在 M E X MEX MEX 序列中出现过的最小的非负整数, 我们得知道一个异或的性质
a ⊕ b = c    ⟺    a ⊕ c = b a ⊕ b = c \iff a ⊕ c = b ab=cac=b

所以我们就是需要找 n ⊕ k ∉ n ⊕ k ∉ nk/ { 0 , 1 , 2 , 3 , , , , m 0,1,2,3,,,,m 0,1,2,3,,,,m } , 且 k k k 最小

所以我们就是要求出 n ⊕ k ≥ m + 1 n ⊕ k ≥ m + 1 nkm+1

n n n 的第 i i i 位为 1 1 1 时, m + 1 m + 1 m+1 的第 i i i 位为 0 0 0 时, k k k 的第 i i i 位应该为 0 0 0, 因为 1 ⊕ 0 = 1 1 ⊕ 0 = 1 10=1, 然后为了保证 k k k 尽可能的小, 所以我们必须使后面的每一个都为 0 0 0

n n n 的第 i i i 位为 1 1 1 时, m + 1 m + 1 m+1 的第 i i i 位为 1 1 1 时, k k k 的第 i i i 位应该为 0 0 0, 因为 1 ⊕ 0 = 1 1 ⊕ 0 = 1 10=1

n n n 的第 i i i 位为 0 0 0 时, m + 1 m + 1 m+1 的第 i i i 位为 0 0 0 时, k k k 的第 i i i 位应该为 0 0 0, 这样就保证了不比 m m m 小且 k k k 最小

n n n 的第 i i i 位为 0 0 0 时, m + 1 m + 1 m+1 的第 i i i 位为 1 1 1 时, k k k 的第 i i i 位应该为 1 1 1, 这样就保证了不比 m m m 小且 k k k 最小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广西小蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值