[Offer收割]编程练习赛51

本文提供了四道算法竞赛题目的详细解答思路及代码实现,包括灯光控制、相似字符串判断、等差子序列查找以及评论框排版优化等问题。通过具体算法和数据结构的应用,解决了实际问题。

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

A:灯光控制

思路:对一条x+k * k1, y+k * k2分类讨论得出能照亮的灯有多少,如果(X, Y)发出的两条射线不在同一个方向,那么就是两条射线能照亮的灯数之和减去1,否则看同一条射线上重复照亮的多少灯,相加之后减去即可。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;

ll n, m;
ll solve(ll x, ll y, ll a, ll b) {
    if(!a && !b) return 1;
    if(!a) {
        if(b > 0) return ((m - y) / b) + 1;
        else return ((1 - y) / b) + 1;
    } else if(!b) {
        if(a > 0) return ((n - x) / a) + 1;
        else return ((1 - x) / a) + 1;
    } else {
        if(a > 0 && b > 0) return min((n - x) / a, (m - y) / b) + 1;
        else if(a > 0 && b < 0) return min((n - x) / a, (1 - y) / b) + 1;
        else if(a < 0 && b < 0) return min((1 - x) / a, (1 - y) / b) + 1;
        else return min((1 - x) / a, (m - y) / b) + 1;
    }
}

ll gcd(ll x, ll y) { return y ? gcd(y, x % y) : x; }

int main() {
    ll x, y;
    ll a, b, c, d;
    while(scanf("%lld %lld %lld %lld", &n, &m, &x, &y) != EOF) {
        scanf("%lld %lld %lld %lld", &a, &b, &c, &d);
        ll ans1 = solve(x, y, a, b);
        ll ans2 = solve(x, y, c, d);
        ll ans;
        if(a * d == c * b && a * c + b * d > 0) {
            if(!a) {
                ll g = gcd(b, d);
                ll lcm = b * d / g;
                if(lcm <= m) ans = ans1 + ans2 - solve(x, y, 0, lcm);
                else ans = ans1 + ans2 - 1;
            } else if(!b) {
                ll g = gcd(a, c);
                ll lcm = a * c / g;
                if(lcm <= n) ans = ans1 + ans2 - solve(x, y, lcm, 0);
                else ans = ans1 + ans2 - 1;
            } else {
                ll g1 = gcd(a, c), g2 = gcd(b, d);
                ll l1 = a * c / g1, l2 = b * d / g2;
                if(l1 <= n && l2 <= m) ans = ans1 + ans2 - solve(x, y, l1, l2);
                else ans = ans1 + ans2 - 1;
            }
        } else {
            ans = ans1 + ans2 - 1;
        }
        cout << ans << endl;
    }
    return 0;
}

B:相似的字符串

思路:对相同长度的字符串哈希26次就好了,为了避免出现哈希值相同的,可以用两个哈希值存储

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;

struct P {
    ll len, hs1, hs2;
    P() {}
    P(ll l, ll h1, ll h2) : len(l), hs1(h1), hs2(h2) {}
    bool operator < (P p) const {
        if(len != p.len) return len < p.len;
        if(hs1 != p.hs1) return hs1 < p.hs1;
        return p.hs2 < p.hs2;
    }
    bool operator == (P p) const {
        if(len != p.len) return false;
        if(hs1 != p.hs1) return false;
        if(hs2 != p.hs2) return false;
        return true;
    }
};
const ll mod1 = 1e15 + 7;
const ll mod2 = 1e14 + 7;
const ll maxn = 1e5 + 10;

map<P, int> mp;
char str[maxn]; int n;

int main() {
    while(scanf("%d", &n) != EOF) {
        mp.clear();
        for(int i = 0; i < n; i++) {
            scanf("%s", str);
            ll len = strlen(str);
            ll s1 = 0, s2 = 0, flag = 0;
            for(ll t = 0; t < 26; t++) {
                s1 = 0; s2 = 0;
                for(int j = 0; j < len; j++) {
                    ll x = (t + str[j] - 'A') % 26;
                    s1 = (s1 * 26 + x) % mod1;
                    s2 = (s2 * 26 + x) % mod2;
                }
                if(mp.count(P(len, s1, s2))) { flag = 1; break; }
            }
            if(!flag) mp[P(len, s1, s2)] = 1;
        }
        int ans = mp.size();
        cout << ans << endl;
    }
    return 0;
}

C:等差子序列

思路:计算每个位置作为右端点能向左扩展到多远,那么线段树中记录下每个节点的等差子序列的区间,按左端点排序,查询区间[ql, qr]的时候,固定了线段树中右端点可以确定哪些在[ql, qr]里面的,左端点要么超出了ql(这些按照右端点最大值取),要么在[ql, qr]里面(这些就是每个点等差子序列区间的长度),所以线段树中维护一个右端点最大值, 一个区间最大值就可以了

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
const int INF = 1e9 + 10;
using namespace std;

typedef pair<int, int> P;
vector<P> G[maxn * 4], data[maxn * 4];
int n, q, ls[maxn], a[maxn];

void build(int o, int l, int r) {
    for(int i = l; i <= r; i++) G[o].push_back(P(i - ls[i] + 1, i));
    sort(G[o].begin(), G[o].end());
    int sz = G[o].size();
    for(int i = 0; i < sz; i++) {
        data[o].push_back(P(0, 0));
        if(!i) data[o][i].first = G[o][i].second;
        else data[o][i].first = max(G[o][i].second, data[o][i - 1].first);
    }
    data[o][sz - 1].second = G[o][sz - 1].second - G[o][sz - 1].first + 1;
    for(int i = sz - 2; i >= 0; i--) data[o][i].second = max(G[o][i].second - G[o][i].first + 1, data[o][i + 1].second);
    if(l == r) return ;
    int mid = (l + r) >> 1, o1 = o << 1, o2 = o << 1 | 1;
    build(o1, l, mid); build(o2, mid + 1, r);
}

int query(int o, int l, int r, int ql, int qr) {
    if(l > qr || r < ql) return 0;
    if(l >= ql && r <= qr) {
        int id = lower_bound(G[o].begin(), G[o].end(), P(ql, 0)) - G[o].begin();
        int a1 = 0, a2 = 0;
        if(id) a1 = data[o][id - 1].first - ql + 1;
        if(id != G[o].size()) a2 = data[o][id].second;
        return max(a1, a2);
    }
    int mid = (l + r) >> 1, o1 = o << 1, o2 = o << 1 | 1;
    int p1 = query(o1, l, mid, ql, qr);
    int p2 = query(o2, mid + 1, r, ql, qr);
    return max(p1, p2);
}


int main() {
    while(scanf("%d %d", &n, &q) != EOF) {
        a[0] = INF;
        for(int i = 0; i < 4 * maxn; i++) { G[i].clear(); data[i].clear(); }
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            if(i == 1) ls[i] = 1;
            else {
                ls[i] = 2;
                if(a[i] - a[i - 1] == a[i - 1] - a[i - 2]) ls[i] = ls[i - 1] + 1;
            }
        }
        build(1, 1, n);
        while(q--) {
            int ql, qr; scanf("%d %d", &ql, &qr);
            cout << query(1, 1, n, ql, qr) << endl;
        }
    }
    return 0;
}

D:评论框排版

思路:如果一个集合里保证这些区间不相交, 相连续的已经合并的话,对于插入[L, R]时,首先考虑和他最近的而且在他前面的,有交集的话就合并这两个, 否则不考虑前面的,合并之后逐个考虑后面的区间,把可以合并的逐个合并, 合并的时候用并查集记录下需要往后移动多少就行了

#include<bits/stdc++.h>
typedef long long ll;
const ll maxn = 1e5 + 10;
const ll INF = 1e9 + 10;
using namespace std;

struct pa {
    ll l, r, node;
    pa() {}
    pa(ll l, ll r, ll n) : l(l), r(r), node(n) {}
    bool operator < (pa p) const { return l < p.l; }
};

ll l[maxn], r[maxn], pre[maxn];
ll dis[maxn], n, x, y;
char op[20];
set<pa> st;


ll findset(ll x) {
    if(x == pre[x]) return x;
    ll af = pre[x];
    ll fa = findset(pre[x]);
    dis[x] = dis[x] + dis[af];
    return pre[x] = fa;
}

void add(ll x) {
    pa now(l[x], r[x], x);
    set<pa>::iterator it;
    it = st.upper_bound(now);
    pa sta = now;
    if(!st.empty() && it != st.begin()) {
        it--; pa res = *it;
        if(res.r >= l[x] - 1) {
            st.erase(it);
            ll rr = res.r;
            ll ds = rr - l[x] + 1;
            dis[x] = ds; pre[x] = res.node;
            res.r = r[x] + ds; sta = res;
        }
    }
    while(!st.empty()) {
        it = st.lower_bound(sta);
        if(it == st.end() || sta.r < (*it).l - 1) break;
        pa res = *it; st.erase(it);
        pre[res.node] = sta.node;
        ll ds = sta.r - res.l + 1;
        dis[res.node] = ds;
        sta.r = res.r + ds;
    }
    st.insert(sta);
}

int main() {
    while(scanf("%lld", &n) != EOF) {
        memset(dis, 0, sizeof dis);
        for(int i = 1; i <= n; i++) pre[i] = i;
        ll cnt = 0; st.clear();
        while(n--) {
            scanf("%s", op);
            if(op[0] == 'I') {
                scanf("%lld %lld", &x, &y);
                cnt++;
                l[cnt] = x; r[cnt] = x + y - 1;
                add(cnt);
            } else {
                scanf("%lld", &x); findset(x);
                ll tot = dis[x];
                printf("%lld %lld\n", l[x] + tot, r[x] + tot);
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值