A-模数的世界
题意:
思路:
先说结论,
如果
a
=
0
,
并
且
b
=
0
的
话
,
那
么
g
c
d
(
a
,
b
)
一
定
是
=
0
a = 0,并且b = 0的话,那么gcd(a,b)一定是 = 0
a=0,并且b=0的话,那么gcd(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)=p−1,那么肯定是p - 1的倍数,那就是 x = k 1 ∗ ( p − 1 ) x = k1 * (p - 1) x=k1∗(p−1), y = k 2 ∗ ( p − 1 ) y = k2 * (p - 1) y=k2∗(p−1),考虑因式分解的形式,既然要与a和b,那么一定p的倍数形式 + a / b +a/b +a/b这种形式,那么我们令 k 1 = ( p − a ) , k 2 = ( p − b ) k1 = (p - a),k2 = (p - b) k1=(p−a),k2=(p−b),很明显就可以解决了.
但是直接赋这个值可能会导致,一开始的 g c d > p − 1 gcd > p - 1 gcd>p−1,取完模之后反而会 < p − 1 < p - 1 <p−1,怎么解决呢,这里一个假算法就是一直乘一个数知道 g c d gcd gcd满足条件,乘 p ∗ ( p − 1 ) p * (p - 1) p∗(p−1),为什么是这个数呢,因为这个数既不会该变在模 p p p条件下和 a a a同余的情况,同时也保证一定是 p − 1 p - 1 p−1的倍数。
正解是用 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;
}