[noip2000-普及] 税收与补贴问题

GitHub跳转原题关系图返回列表

逐价位建立关于补贴额 t 的一次不等式,取上下界交集后在可行区间中选绝对值最小的整数。

OJ: roj

题目 ID: 3499

难度:普及

标签:数学python

创建: 2026-10-02 03:29

更新: 2026-10-06 12:12

题目描述

已知商品成本 cc、成本价位销量 s0s_0 与若干(价位,销量)点,相邻已知点间销量线性变化;超过最高已知价位后,每涨 1 元销量减少 dd。设政府预期价为 PP,政府对每件商品收税或补贴整数 tt(t>0t>0 为补贴,t<0t<0 为收税),总利润 =(=(价位 −c+t)×-c+t)\times 销量。求使 PP 处总利润最大的最小 ∣t∣|t|;不存在则输出 NO SOLUTION。

思路

每个可售价位 pp 都给出一个关于 tt 的一次不等式,按销量系数大小分别得到 tt 的下界、上界或无解判定;逐价位扫描并维护可行区间 [lo,hi][lo,hi],最后在其中取绝对值最小的整数。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 04:25
# update_at: 2026-10-02 04:25

import sys
from bisect import bisect_right

INF = 10**18          # 还没产生某一侧约束时的区间哨兵
PRICE_CAP = 300000     # 销量归零前的价位枚举上限(题面所有数字 < 100000)


def sales(xs: list[int], ss: list[int], drop: int, price: int) -> tuple[int, int]:
    """价位 price 的销量,返回 (分子, 分母):已知价位间线性插值,超过最高价位后每涨 1 元减 drop。"""
    i = bisect_right(xs, price) - 1          # price 落在 xs[i] ~ xs[i+1] 的插值段内
    if i + 1 < len(xs):
        p0, p1 = xs[i], xs[i + 1]
        # 段内销量 = ss[i] + (ss[i+1]-ss[i])/(p1-p0) * (price-p0),先不约分
        return ss[i] * (p1 - p0) + (ss[i + 1] - ss[i]) * (price - p0), p1 - p0
    return ss[i] - drop * (price - xs[i]), 1  # 超出最高价位:按固定数值递减


def ceil_div(a: int, b: int) -> int:
    """向上取整的整数除法,要求 b > 0。"""
    return -((-a) // b)


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    target = next(data)                          # 政府预期价
    cost, low_sales = next(data), next(data)     # 成本价、成本价位上的销量

    known: dict[int, int] = {cost: low_sales}    # 已知价位 → 销量,按价位去重
    while True:
        price, count = next(data), next(data)
        if price == -1 and count == -1:
            break
        known[price] = count
    drop = next(data)                            # 超出最高价位后每涨 1 元减少的销量

    xs = sorted(known)                           # 升序价位表,供二分定位插值段
    ss = [known[p] for p in xs]

    if target < cost:              # 预期价低于成本,产品不会在该价位销售
        print("NO SOLUTION")
        return
    target_num, target_den = sales(xs, ss, drop, target)
    if target_num <= 0:            # 预期价位上没有销量,谈不上最大总利润
        print("NO SOLUTION")
        return

    base = target - cost           # 预期价位的不含税单位利润
    lo, hi = -INF, INF             # 补贴额 t 最终落在区间 [lo, hi]

    for price in range(cost, PRICE_CAP + 1):
        num, den = sales(xs, ss, drop, price)
        if num <= 0:               # 销量随价位单调不增,后面价位都没有销量
            break
        gain = price - cost        # 该价位的不含税单位利润
        # 要求 (base+t)·target_num/target_den ⩾ (gain+t)·num/den,
        # 两边乘正数 target_den·den 后是关于 t 的一次不等式:
        target_coef = target_num * den   # 左边 t 的系数
        price_coef = num * target_den    # 右边 t 的系数
        if target_coef == price_coef:
            # 销量相同:不含税单位利润必须不低,否则任何 t 都救不回来
            if gain > base:
                print("NO SOLUTION")
                return
        elif target_coef > price_coef:    # t 有下界
            lo = max(lo, ceil_div(price_coef * gain - target_coef * base,
                                  target_coef - price_coef))
        else:                            # t 有上界
            hi = min(hi, (price_coef * gain - target_coef * base)
                          // (target_coef - price_coef))

    if lo > hi:
        print("NO SOLUTION")
    else:
        # 区间内取绝对值最小:跨 0 取 0,否则取离 0 最近的端点
        print(0 if lo <= 0 <= hi else min((lo, hi), key=abs))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 12:10
 * update_at: 2026-10-06 12:10
 */
#include <cstdio>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

typedef long long ll;
typedef __int128 i128;

const int MAXP = 300000;      // 价位扫描上限,防止销量永不归零时死循环

ll P;                         // 政府预期价
ll cost;                      // 商品成本
vector<ll> xs;                // 升序已知价位表
vector<ll> ss;                // 对应销量
ll drop;                      // 超过最高已知价位后每涨 1 元减少的销量

// 计算 a/b 的上取整,b != 0
ll ceil_div(i128 a, i128 b) {
    i128 q = a / b;
    i128 r = a % b;
    if (r != 0 && ((r > 0) == (b > 0))) ++q;
    return (ll)q;
}

// 计算 a/b 的下取整,b != 0
ll floor_div(i128 a, i128 b) {
    i128 q = a / b;
    i128 r = a % b;
    if (r != 0 && ((r > 0) != (b > 0))) --q;
    return (ll)q;
}

// 返回价位 p 的销量,以分数 num/den 表示(den > 0)
void get_sales(ll p, ll &num, ll &den) {
    int i = upper_bound(xs.begin(), xs.end(), p) - xs.begin() - 1;
    if (i + 1 < (int)xs.size()) {
        ll p0 = xs[i], p1 = xs[i + 1];
        den = p1 - p0;
        num = ss[i] * den + (ss[i + 1] - ss[i]) * (p - p0);
    } else {
        den = 1;
        num = ss[i] - drop * (p - xs[i]);
    }
}

// 输出 __int128 整数
void print_i128(i128 x) {
    if (x == 0) {
        putchar('0');
        return;
    }
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    char buf[50];
    int len = 0;
    while (x > 0) {
        buf[len++] = '0' + (int)(x % 10);
        x /= 10;
    }
    while (len--) putchar(buf[len]);
}

int main() {
    scanf("%lld", &P);

    ll s0;
    scanf("%lld%lld", &cost, &s0);

    map<ll, ll> known;
    known[cost] = s0;

    while (true) {
        ll p, s;
        scanf("%lld%lld", &p, &s);
        if (p == -1 && s == -1) break;
        known[p] = s;
    }

    scanf("%lld", &drop);

    for (auto &kv : known) {
        xs.push_back(kv.first);
        ss.push_back(kv.second);
    }

    // 预期价不能低于成本,否则产品不会在该价位销售
    if (P < cost) {
        printf("NO SOLUTION\n");
        return 0;
    }

    ll t_num, t_den;
    get_sales(P, t_num, t_den);
    // 预期价位上没有销量,谈不上最大总利润
    if (t_num <= 0) {
        printf("NO SOLUTION\n");
        return 0;
    }

    ll base = P - cost;           // 预期价位的不含税单位利润
    i128 lo = 0, hi = 0;
    bool has_lo = false, has_hi = false;

    // 从成本价开始逐价位扫描,销量非正即停止
    for (ll p = cost; p <= MAXP; ++p) {
        ll num, den;
        get_sales(p, num, den);
        if (num <= 0) break;

        ll gain = p - cost;
        // 要求 (base+t) * t_num/t_den >= (gain+t) * num/den
        i128 target_coef = (i128)t_num * den;   // 左边 t 的系数
        i128 price_coef = (i128)num * t_den;    // 右边 t 的系数

        if (target_coef == price_coef) {
            // 销量相同:该价位不含税单位利润不能更高,否则任何 t 都无法使 P 最大
            if (gain > base) {
                printf("NO SOLUTION\n");
                return 0;
            }
        } else if (target_coef > price_coef) {
            // t 有下界
            i128 rhs = (i128)price_coef * gain - (i128)target_coef * base;
            i128 dif = target_coef - price_coef; // > 0
            i128 bound = ceil_div(rhs, dif);
            if (!has_lo || bound > lo) {
                lo = bound;
                has_lo = true;
            }
        } else {
            // t 有上界
            i128 rhs = (i128)price_coef * gain - (i128)target_coef * base;
            i128 dif = target_coef - price_coef; // < 0
            i128 bound = floor_div(rhs, dif);
            if (!has_hi || bound < hi) {
                hi = bound;
                has_hi = true;
            }
        }
    }

    // 无可行补贴额
    if (has_lo && has_hi && lo > hi) {
        printf("NO SOLUTION\n");
        return 0;
    }

    i128 ans;
    if ((!has_lo || lo <= 0) && (!has_hi || hi >= 0)) {
        ans = 0;                // 可行区间包含 0,取绝对值最小
    } else if (has_hi && hi < 0) {
        ans = hi;               // 全负,取右端点
    } else {
        ans = lo;               // 全正,取左端点
    }

    print_i128(ans);
    putchar('\n');
    return 0;
}