GDCPC广东省大学生程序设计竞赛部分题解

Industrial Nuclear Water(计算几何)3星

题意:

给你两个点,问这两个点是否在所给三个面的同一侧

1000 ∣ x ∣ = y 2 + z 2 , 1000 ∣ y ∣ = x 2 + z 2 , 1000 ∣ z ∣ = x 2 + y 2 1000|x|=y^2+z^2,1000|y|=x^2+z^2,1000|z|=x^2+y^2 1000x=y2+z2,1000y=x2+z2,1000z=x2+y2

题解:

判断两点是否在一个面的同一侧,只需将点带入方程看结果是否符号一致

#include<bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=1010;
int n,m;
ll a[5],b[5];
bool get(ll a,ll b,ll c){
    return 1000*a<=b*b+c*c;
}
void solve(){
    for(int i=0;i<3;i++) cin>>a[i];
    for(int i=0;i<3;i++) cin>>b[i];
    int f=1;
    for(int i=0;i<3;i++){
        if(get(a[(0+i)%3],a[(1+i)%3],a[(2+i)%3])!=get(b[(0+i)%3],b[(1+i)%3],b[(2+i)%3])){
            f=0;
            break;
        }
        if(get(-a[(0+i)%3],a[(1+i)%3],a[(2+i)%3])!=get(-b[(0+i)%3],b[(1+i)%3],b[(2+i)%3])){
            f=0;
            break;
        }
    }
    if(f) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T=1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

An Easy Problem(思维+二分/堆)4星

题意:

求出 i ∗ j ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) i*j(1\leq i\leq n,1\leq j\leq m) ij(1in,1jm)中的第 k k k大数

题解:

1)堆:

显然最大值一定在最后面的一列

将第 i i i行的最后一个元素插入堆中,每次取出其中的最大值,换为这一行的第二大值

#include<bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll,ll>PII;
const int N=1010;
ll n,m,k;
priority_queue<PII>q;
void solve(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) q.push({i*m,i});
    while(k--){
        auto [x,y]=q.top();
        q.pop();
        q.push({x-y,y});
    }
    cout<<q.top().x;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

2)二分:

转化为求第k小数,二分check对于每个 i i i小于等于 m i d mid mid的数的个数是否大于等于k

#include<bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll,ll>PII;
const int N=1010;
ll n,m,k;
bool check(ll mid){
    ll res=0;
    for(int i=1;i<=n;i++){
        res+=min(m,mid/i);
    }
    return res>=k;
}
void solve(){
    cin>>n>>m>>k;
    k=n*m-k+1;
    ll l=1,r=1e12,ans=r;
    while(l<=r){
        ll mid=l+r>>1;
        if(check(mid)) ans=min(ans,mid),r=mid-1;
        else l=mid+1;
    }
    cout<<ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

Byfibonacci(DP)4星

题意:

T组询问,求出 w ( F ( x ) ) w(F(x)) w(F(x)),每一种用斐波那契数构成的x的方案的价值的和

方案的价值指涉及的每个斐波那契额数的乘积

题解:

每个数一定由小于等于本身的最大和次大斐波那契额数构成

f[i]=(f[i-g[pos]]*g[pos]+f[i-g[pos-1]]*g[pos-1])%mod;

次大值会被重复计算,所以当满足 次 大 值 ∗ 2 ≤ 当 前 值 次大值*2\leq 当前值 2时要减去重复计算的那一部分

#include<bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=1e7+10,mod=998244353;
int n,m;
ll f[N],g[110];
int vis[N],pos;
void init(){
    g[2]=1,g[1]=1;
    for(int i=3;;i++){
        g[i]=g[i-1]+g[i-2];
        if(g[i]>N) break;
        m=i;vis[g[i]]=i;
    }
    f[0]=1,f[1]=2,f[2]=3;
    for(int i=3;i<=1e7;i++){
        if(vis[i]) pos=vis[i];
        f[i]=(f[i-g[pos]]*g[pos]+f[i-g[pos-1]]*g[pos-1])%mod;
        if(2*g[pos-1]<=i)//减去重复计算的一部分
            f[i]=((f[i]-f[i-2*g[pos-1]]*g[pos-1]%mod*g[pos-1]%mod)%mod+mod)%mod;
    }
}
void solve(){
    cin>>n;
    cout<<f[n]<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    init();
    int T=1;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

Kera’s line segment(区间转二维平面点+二维树状数组)4星

题意:

就是一个一维的数轴上面有一堆线段用一个三元组$(l , r , v a l ) $表示。
现在我们有两个操作:

  1. 就是往数轴上面添加线段
  2. 询问 [ L , R ] [ L , R ] [L,R] 这个区间里面线段权值最大跟最小的差值是多少??(只有被这个$[ L , R ] $区间完全覆盖才算在里面)

1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105

1 ≤ l , r ≤ 3000 1\leq l,r\leq 3000 1l,r3000

题解:

如何维护一个线段(区间)?

我们需要将每个区间映射成一个点,所以可以将区间 [ l , r ] [l,r] [l,r]转换为一个二维坐标 ( l , r ) (l,r) (l,r),查询区间 [ L , R ] [L,R] [L,R]就是查询左上角为 ( L , R ) (L,R) (L,R),右下角为 ( m a x l , m a x r ) (max_l,max_r) (maxl,maxr)的区间

为方便取前缀和,可以将区间 [ l , r ] [l,r] [l,r]转化为点 ( 3010 − l , r ) (3010-l,r) (3010l,r),这样就可以用左上角为 ( 1 , 1 ) (1,1) (1,1),右下角为 ( 3010 − L , R ) (3010-L,R) (3010L,R)的区域表示 [ L , R ] [L,R] [L,R]

前缀和用树状数组维护

#include<bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=3010;
int tr_ma[N][N],tr_mi[N][N];
int n,m;
inline int lowbit(int x){
    return x&-x;
}
inline void add(int a,int b,int v){
    int t=b;
    while(a<N){
        b=t;
        while(b<N){
            tr_ma[a][b]=max(tr_ma[a][b],v);
            tr_mi[a][b]=min(tr_mi[a][b],v);
            b+=lowbit(b);
        }
        a+=lowbit(a);
    }
}
inline int query(int a,int b){
    int t=b,ma=-1,mi=INF;
    while(a){
        b=t;
        while(b){
            ma=max(ma,tr_ma[a][b]);
            mi=min(mi,tr_mi[a][b]);
            b-=lowbit(b);
        }
        a-=lowbit(a);
    }
    return ma-mi;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    memset(tr_mi,0x3f,sizeof tr_mi);
    cin>>n>>m;
    while(n--){
        int a,b,v; cin>>a>>b>>v;
        add(N-a,b,v);
    }
    int last=0;
    while(m--){
        int f,a,b,v; cin>>f;
        if(f==1){
            cin>>a>>b>>v;
            a^=last,b^=last;
            add(N-a,b,v);
        }
        else{
            cin>>a>>b;
            a^=last,b^=last;
            last=query(N-a,b);
            cout<<last<<endl;
        }
    }
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值