2- noip模拟赛 DAY2

本文介绍了解决勾股数问题的算法实现,并探讨了区间和及最长上升子序列问题的有效解决方法。针对不同输入类型(奇数或偶数),文章提供了具体的计算公式和C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1 勾股数

因为a2=c2b2,所以可以用平方差公式展开,a2=(cb)(c+b)
分情况考虑:
当输入的a为奇数时,
cb=1,则c+b=a2,所以2b+1=a2,解得:

{b=a212c=a212+1

当输入的a为偶数时
cb=2,则c+b=a22,所以2b+2=a22,解得:
b=a241c=a24+1
    #include <iostream>
    #include <cstdio>
    using namespace std;
    typedef long long ll;
    int main(){
        ll a;
        cin >> a;
        if(a<=2) cout << "-1";
        else if(a&1) cout << (a*a-1)/2 << " " << ((a*a-1)/2)+1;
        else cout << (a*a)/4-1 << " " << ((a*a)/4)+1;
        return 0;
    }

T2 区间和

先将原问题转化为b序列前k小的元素的和。由于数列中每个数的都大于零,所以可以二分b序列第k小的元素的大小。给定x,求b序列中小于等于x的元素个数有两种方法:1.枚举a的区间的左端点l(一共有n个),然后再一次二分,求出最右的右端点r使得ri=la[i]x,复杂度O(nlogn);2.一次dp求出所有左端点对应的右端点,复杂度O(n)。最后复杂度还要乘上二分的复杂度O(logn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n, m, val, num;
ll a1[100010], sum[100010], summ[100010];
void check(ll x){
    ll p = 0;
    for(ll i = 1; i <= n; i ++){
        p = max(p, i-1);
        while(p+1 <= n && sum[p+1] - sum[i-1] <= x) p ++;
        val += (summ[p] - summ[i-1] - (p-i+1)*sum[i-1]);
        num += (p-i+1);
    }
}
ll solve(ll x){
    ll l = 0, r = 1e7, ans = 0, res = 0, cnt = 0;
    while(l <= r){
        ll mid = (l+r) >> 1;
        val = 0, num = 0, check(mid);
        if(num >= x) r = mid - 1, ans = mid, res = val, cnt = num;
        else l = mid + 1; 
    }
    return res - (cnt-x) * ans;
}
int main(){
    freopen("sum.in", "r", stdin);
    freopen("sum.out", "w", stdout);
    scanf("%lld%lld", &n, &m);
    for(ll i = 1; i <= n; i ++) scanf("%lld", &a1[i]);
    for(ll i = 1; i <= n; i ++) sum[i] = sum[i-1] + a1[i], summ[i] = summ[i-1] + sum[i];
    for(ll i = 1; i <= m; i ++){
        ll a, b;
        scanf("%lld%lld", &a, &b);
        printf("%lld\n", solve(b) - solve(a-1));
    }
    return 0;
} 

T3 最长上升子序列

0可以转化成任意整数,包括负数,显然求LIS时尽量把0都放进去必定是正确的。因此我们可以把0拿出来,对剩下的做O(nlogn)的LIS,统计结果的时候再算上0的数量。为了保证严格递增,我们可以将每个权值a[i]减去i前面0的个数,再做LIS,就能保证结果是严格递增的。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int inf = 1e9;
int f[100010], d[100010], a1[100010];
int n, cnt, ans;
inline int read(){
    char c;
    int ok = 0, num = 0;
    while(c = getchar()){
        if(c <= '9' && c >= '0') 
            ok = 1, num = num*10+c-'0';
        else if(ok) return num;
    }
}
inline int find(int l, int r, int val){
    int ans1 = 0;
    while(l <= r){
        int mid = (l+r)>>1;
        if(d[mid] >= val) r = mid-1;
        else ans1 = mid,l = mid+1;
    }
    return ans1;
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) d[i] = inf; d[0] = -inf;
    for(int i = 1; i <= n; i ++) a1[i] = read();
    for(int i = 1; i <= n; i ++){
        if(a1[i] == 0){
            d[ans+1] = d[ans]+1;
            for(int j = ans; j >= 1; j --)
                if(d[j] > d[j-1]+1)
                    d[j] = d[j-1]+1;
            f[i] = ++ ans;
            continue;
        }
        f[i] = find(0,i-1,a1[i])+1;
        if(a1[i] < d[f[i]]) d[f[i]] = a1[i];
        if(f[i] > ans) ans = f[i];
    }
    printf("%d", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值