2020牛客寒假算法基础训练营3

2020牛客寒假算法基础训练营3

A-模数的世界

题意:
在这里插入图片描述
思路:
先说结论,
如果 a = 0 , 并 且 b = 0 的 话 , 那 么 g c d ( a , b ) 一 定 是 = 0 a = 0,并且b = 0的话,那么gcd(a,b)一定是 = 0 a=0b=0gcd(a,b)=0
否则的话,我们一定能构造出来x和y,使得gcd(x,y) = p - 1。

既然要 g c d ( x , y ) = p − 1 gcd(x,y) = p - 1 gcd(x,y)=p1,那么肯定是p - 1的倍数,那就是 x = k 1 ∗ ( p − 1 ) x = k1 * (p - 1) x=k1(p1) y = k 2 ∗ ( p − 1 ) y = k2 * (p - 1) y=k2(p1),考虑因式分解的形式,既然要与a和b,那么一定p的倍数形式 + a / b +a/b +a/b这种形式,那么我们令 k 1 = ( p − a ) , k 2 = ( p − b ) k1 = (p - a),k2 = (p - b) k1=(pa),k2=(pb),很明显就可以解决了.

但是直接赋这个值可能会导致,一开始的 g c d > p − 1 gcd > p - 1 gcd>p1,取完模之后反而会 < p − 1 < p - 1 <p1,怎么解决呢,这里一个假算法就是一直乘一个数知道 g c d gcd gcd满足条件,乘 p ∗ ( p − 1 ) p * (p - 1) p(p1),为什么是这个数呢,因为这个数既不会该变在模 p p p条件下和 a a a同余的情况,同时也保证一定是 p − 1 p - 1 p1的倍数。

正解是用 e x g c d exgcd exgcd,但是因为我看不懂推导,只能参考 兰子巨巨 的思路了。

代码:

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int MAXN = 2e4 + 7;
//如果 a != 0 && b != 0,那么gcd(a,b) != 0
//并且这个值最大一定是p-1,一定能够构造出来 所以构造的时候肯定是有(p-1)的 , 再考虑因式分解就可以知道(p-1)*(p-a) 和 a 模p同余
//同理 (p-1) * (p-b) 与 b 模p同余
//但是好像直接这样构造会出现 gcd(x,y)大于p-1取模之后反而更小了
//所以可以用一个假算法 不断+(p-1)*p 因为这个数既不会改变与a模p的同余关系 同时保证了gcd也一定是p-1 但是无法保证能在题目要求的范围内构造出来
//正解应该还是 exgcd 求解
ll gcd(ll a,ll b) {
	ll r;
	while(a % b != 0) { r = a % b; a = b; b = r; }
	return b;
}

int main() {
	int T;scanf("%d",&T);
	while(T--) {
		ll a,b,p;scanf("%lld %lld %lld",&a,&b,&p);
		if(a == 0 && b == 0) {
			printf("0 0 0\n");
			continue;
		}
		ll x = (p - 1) * (p - a),y = (p - 1) * (p - b);
		while(gcd(x,y) != p - 1) {
			x += p * (p - 1);
			// y += p * (p - 1);
		}
		printf("%lld %lld %lld\n",p - 1,x,y);
	}
	return 0;
}

B-内卷

题意:
在这里插入图片描述
思路:
方法一:
模拟所有人从最低的等级开始往高等级边,每次选出当前分数最小的人,把它的等级提升一下,同时更新最大值与最小值的差,一直到A等的人数要超过k或者或者一个人的A等级比其他人的所有等级分数都少也不必做了,因为选A的人数小于等于k即可,这时再选最小值也不会变,而最大值却会不断增加,显然不是最优策略。
只需要用一个优先队列维护,最小值升等级这个过程就好了。
代码:

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int MAXN = 51e5 + 10;
int n,k;
struct node {
	int val,id,grade;
	node(int val,int id,int grade) { this->val = val,this->id = id,this->grade = grade; }
	bool operator < (const node &a)const {
		return val > a.val;
	}
};	
priority_queue<node>q;
int a[MAXN][5];

int main() {
	scanf("%d%d",&n,&k);
	int x,ma = 0;
	for(int i = 1;i <= n;i ++) {
		for(int j = 0;j < 5;j ++) {
			scanf("%d",&a[i][j]);
		}
		node t(a[i][4],i,4);//直接从最低的等级去贪心
		q.push(t);
		ma = max(ma,a[i][4]);
	}
	int ans = ma - q.top().val;
	while(true) {
		node now = q.top();
		q.pop();
		if(now.grade == 0) break;//有某一个人的最低等级都比其他人小
		node nex(a[now.id][now.grade-1],now.id,now.grade-1);
		q.push(nex);
		ma = max(ma,a[now.id][now.grade-1]);
		if(now.grade == 1) k--;
		if(k < 0) break;
		ans = min(ans,ma-q.top().val);
	}
	printf("%d\n",ans);
	return 0;
}

方法二:
把所有人的成绩排序之后,其实题目的要求就变成了寻找一个区间,然后这个区间内的A等级小于等于k,并且n个人都被选取了,然后求最小值。
一个贪心的技巧就是,既然A等级要小于等于k,那么肯定是能尽量不选就尽量不选,只有迫不得已才去选它,按照这个原则去添加和删除。这个区间数量和等级要求满足尺取的关系,因此我们又可以在这个区间内进行尺取。

代码:

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int MAXN = 5e5 + 10;

int n,k;
struct nnode {
	int val,id,fg;//值 编号 是否是a等级
	// nnode(int val,int id,int fg){ this->val = val,this->id = id,this->fg = fg; }
}node[MAXN];
bool cmp(nnode a,nnode b){ return a.val < b.val; }

int num,anum,vis[MAXN];
std::map<int,int>mp;
bool check() {
	return mp.size() == n && anum <= k;
}
//按照能不选A就不选A的贪心原则模拟添加和删除的过程
void add(int pos) {
	mp[node[pos].id]++;
	if(node[pos].fg) {//A等级
		if(mp[node[pos].id] == 1) ++anum;
		vis[node[pos].id] = 1;
	}
	else if(mp[node[pos].id] == 2 && vis[node[pos].id] == 1){
		--anum;
		//vis不要动 因为vis还关系到后面的删除操作
	}
}
void del(int pos) {
	if(node[pos].fg) {
		if(mp[node[pos].id] == 1) --anum;
		vis[node[pos].id] = 0;//代表A等级点已经被删除
	}
	else if(mp[node[pos].id] == 2 && vis[node[pos].id] == 1)
		++anum;//删除之后就只剩一个为A等级的点了 所以此时必须选它
	--mp[node[pos].id];
	if(mp[node[pos].id] == 0) mp.erase(node[pos].id);
}

int main() {
	scanf("%d%d",&n,&k);
	int x;
	for(int i = 1;i <= n * 5;i ++) {
		scanf("%d",&x);
		node[i] = {x,(i-1)/5+1,i%5 == 1};
		// cout<<node[i].val<<' '<<node[i].id<<' '<<node[i].fg<<'\n';
	}
	sort(node + 1,node + 1 + n * 5,cmp);
	int r = 1,ans = INF;
	for(int l = 1;l <= n * 5;l ++) {
		if(l-1) del(l-1);
		while(r <= n * 5 && !check()) add(r++);
		// if(check())cout<<l<<' '<<r<<'\n';
		if(check())ans = min(ans,node[r-1].val - node[l].val);
	}
	printf("%d\n",ans);
	return 0;
}

C-重力坠击

题意:
在这里插入图片描述
思路:
这么小的范围-7到7直接枚举+爆搜就可以了。
代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 7;

struct Circle {
	int x,y,r;
}circle[100 + 10];

int ans,n,k,R;
int attack[100 + 10][2];

bool check(int x1,int y1,int r1,int x2,int y2,int r2) {
	int dis = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
	int rd = (r1 + r2) * (r1 + r2);
	if(dis > rd) return false;
	else return true;
}

void dfs(int cnt) {
	if(cnt == k) {
		int sum = 0;
		for(int j = 1;j <= n;j ++) {
			for(int i = 1;i <= cnt;i ++) {
				if(check(attack[i][0],attack[i][1],R,circle[j].x,circle[j].y,circle[j].r)) {
					sum ++;
					break;
				}
			}
		}
		// cout<<sum<<"***\n";
		ans = max(ans,sum);
		return ;
	}
	for(int i = -7;i <= 7;i ++) {
		for(int j = -7;j <= 7;j ++) {
			attack[cnt+1][0] = i;
			attack[cnt+1][1] = j;
			dfs(cnt+1);
		}
	}
}

int main() {
	// int n,k,R;
	scanf("%d%d%d",&n,&k,&R);
	for(int i = 1;i <= n;i ++) {
		scanf("%d%d%d",&circle[i].x,&circle[i].y,&circle[i].r);
	}
	dfs(0);
	printf("%d\n",ans);
	return 0;
}

E-买礼物

题意:
在这里插入图片描述
思路:
可以看到对于查询操作我们只需要回答是否存在即可。那么就模拟一个链表去记录当前位置的数,例如为x,x上一次出现的最近位置,x下一次出现的最近位置。修改操作本质就标成了链表的删除节点操作,然后用线段树维护链表的next指针的位置,如果查询区间内的next指针最小值小于等于r的话,就证明这个区间内是存在两个不同的点的。
切记,当前节点删除,这个节点的值和它上个节点以及下个节点对应的值都应该修改,线段树修改需要修改当前节点和上个节点的相关值。

代码:

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int MAXN = 5e5 + 7;
int n,q,a[MAXN];

int mi[MAXN<<2],nex[MAXN],last[MAXN];
void pushup(int rt){
	mi[rt] = min(mi[lson],mi[rson]);
}
void build(int rt,int l,int r) {
	if(l == r) {
		mi[rt] = nex[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(lson,l,mid);build(rson,mid+1,r);
	pushup(rt);
}
void modify(int rt,int l,int r,int p,int v) {
	if(l == r) {
		mi[rt] = v;return ;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) modify(lson,l,mid,p,v);
	else modify(rson,mid+1,r,p,v);
	pushup(rt);
}
int query(int rt,int l,int r,int L,int R) {
	if(l >= L && r <= R) return mi[rt];
	int mid = (l + r) >> 1,ans = INF;
	if(L <= mid) ans = min(ans,query(lson,l,mid,L,R));
	if(R > mid) ans = min(ans,query(rson,mid+1,r,L,R));
	return ans;
}

std::map<int,int>mp;
//用一个链表维护相同编号的位置前后位置关系
//当前节点指向的是下一个和此节点值相同的位置 用线段树去维护这个关系 从而快查快改
int main() {
	scanf("%d%d",&n,&q);
	for(int i = 1;i <= n;i ++) {
		scanf("%d",&a[i]);
	}
	for(int i = n;i >= 1;i --) {
		if(!mp[a[i]]) nex[i] = n + 1;
		else nex[i] = mp[a[i]];
		mp[a[i]] = i;
	}
	mp.clear();
	for(int i = 1;i <= n;i ++) {
		if(!mp[a[i]]) last[i] = 0;
		else last[i] = mp[a[i]];
		mp[a[i]] = i;
	}
	build(1,1,n);
	// nex[n + 1] = n + 1;
	int op,x,l,r;
	while(q--) {
		scanf("%d",&op);
		if(op == 1) {
			scanf("%d",&x);
			modify(1,1,n,x,n + 1);
			if(last[x]) {
				nex[last[x]] = nex[x];
				last[nex[x]] = last[x];
				modify(1,1,n,last[x],nex[x]);
			}
		}
		else {
			scanf("%d%d",&l,&r);
			int min_pos = query(1,1,n,l,r);
			if(min_pos <= r) puts("1");
			else puts("0");
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值