noip8&&多校2

11.15

t1

赛时想假2.75h,敲了10k(咋做到的?不知道)

曼哈顿距离转切比雪夫距离。

详见

直接出结论:

将每个坐标 \((x,y)\) 变作 \((x+y,x−y)\) 后,原坐标的曼哈顿距离等于新坐标的切比雪夫距离

证明:

拆式子即可。

\[\begin{aligned} |x_i-x_j|+|y_i-y_j|&=\max(x_i-x_j+y_i-y_j,x_j-x_i+y_i-y_j,x_i-x_j+y_j-y_i,x_j-x_i+y_j-y_i)\\ &=\max(x_i+y_i-x_j-y_j,x_j-y_j+y_i-x_i,x_i-y_i-x_j+y_j,x_j+y_j-x_i-y_i)\\ &=\max(∣(x_i+y_i)−(x_j+y_j)∣,∣(x_i−y_i)−(x_j−y_j)∣) \end{aligned} \]

即转为\((x+y,x-y)\)的切比雪夫距离。

然后这题就只需要维护维护\(x+y\)的最大值与最小值\(x-y\)的最大值与最小值即可。

旋转手推式子。

\((x,y)-->(-y,x)\)

对应的

\((x+y,x-y)-->(x-y,-x-y)\)

按式子转移即可。

code

t1
#include <bits/stdc++.h>
#define pir pair<int, int>
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 10;
int n, m;
pir a[N];
struct tree
{
    int l, r;
    int mxx, mnx, mxy, mny;
    int laz;
} t[N << 2];
#define lid (id << 1)
#define rid (id << 1 | 1)

inline void pushup(int id)
{
    t[id].mxx = max(t[lid].mxx, t[rid].mxx);
    t[id].mnx = min(t[lid].mnx, t[rid].mnx);
    t[id].mxy = max(t[lid].mxy, t[rid].mxy);
    t[id].mny = min(t[lid].mny, t[rid].mny);
}

inline void change(int id)
{
    swap(t[id].mxx, t[id].mny);
    swap(t[id].mnx, t[id].mxy);
    swap(t[id].mnx, t[id].mxx);
    t[id].mxy = -t[id].mxy, t[id].mny = -t[id].mny;
}

// (x,y)-->(-y,x)

inline void pushdown(int id)
{
    if (!t[id].laz)
        return;
    for (int i = 1; i <= t[id].laz; ++i)
        change(lid), change(rid);
    (t[lid].laz += t[id].laz) %= 4;
    (t[rid].laz += t[id].laz) %= 4;
    t[id].laz = 0;
}

inline void build(int id, int l, int r)
{
    t[id].l = l, t[id].r = r;
    if (l == r)
    {
        t[id].mxx = t[id].mnx = a[l].fi + a[l].se;
        t[id].mxy = t[id].mny = a[l].fi - a[l].se;
        return;
    }
    int mid = (l + r) >> 1;
    build(lid, l, mid), build(rid, mid + 1, r);
    pushup(id);
}

inline void update(int id, int l, int r)
{
    if (l <= t[id].l && t[id].r <= r)
    {
        change(id);
        (t[id].laz += 1) %= 4;
        return;
    }
    pushdown(id);
    int mid = (t[id].l + t[id].r) >> 1;
    if (mid >= l)
        update(lid, l, r);
    if (mid < r)
        update(rid, l, r);
    pushup(id);
}

inline tree query(int id, int l, int r)
{
    if (l <= t[id].l && t[id].r <= r)
        return t[id];
    pushdown(id);
    tree ans = {0, 0, INT_MIN, INT_MAX, INT_MIN, INT_MAX, 0};
    tree ans2;
    int mid = (t[id].l + t[id].r) >> 1;
    if (mid >= l)
    {
        ans2 = query(lid, l, r);
        ans.mxx = max(ans.mxx, ans2.mxx);
        ans.mnx = min(ans.mnx, ans2.mnx);
        ans.mxy = max(ans.mxy, ans2.mxy);
        ans.mny = min(ans.mny, ans2.mny);
    }
    if (mid < r)
    {
        ans2 = query(rid, l, r);
        ans.mxx = max(ans.mxx, ans2.mxx);
        ans.mnx = min(ans.mnx, ans2.mnx);
        ans.mxy = max(ans.mxy, ans2.mxy);
        ans.mny = min(ans.mny, ans2.mny);
    }
    return ans;
}

inline void debug()
{
    for (int i = 1; i <= n; ++i)
    {
        cout << "i=" << i << "\n";
        tree ans = query(1, i, i);
        cout << "mxx=" << ans.mxx << " mnx=" << ans.mnx << " mxy=" << ans.mxy << " mny=" << ans.mny << "\n";
    }
    cout << "------------------------------\n";
}

signed main()
{
    freopen("fish.in", "r", stdin);
    freopen("fish.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i].fi >> a[i].se;
    build(1, 1, n);
    int opt, l, r;
    while (m--)
    {
        // debug();
        cin >> opt >> l >> r;
        if (opt == 1)
            update(1, l, r);
        else
        {
            tree ans = query(1, l, r);
            cout << max(abs(ans.mxx - ans.mnx), abs(ans.mxy - ans.mny)) << "\n";
        }
    }
    return 0;
}

/*

x=x+y
y=x-y

laz=1
(x,y)-->(-y,x)    (x+y,x-y)-->(-(x-y),x+y)

*/

t2

这道题感觉很巧妙,转换成图论后很好写。

首先发现了每行选一每列选一且不能重可能会想到二分图状物(没想到怎么办?不知道,我就没想到)。

但发现数据范围是1e5级别,硬跑肯定不行。

观察,发现若我们将行和列抽象成点,每个物品向对应的行与列连边,则实际上是将该物品对应的行与列连通,边权为该物品对应价值

连通性用并查集即可轻松维护。

于是我们可以贪心的求解。

具体的,按权值降序排列,对于每个物品,将其行与列对应点合并,同时维护siz(连通块的大小)和has(该连通块选了几个点),若\(siz_u>has_u\)则可以选该物品,答案累加上但前权值,否则跳过即可。

若不理解就感性理解一下(感觉我讲的不是很清楚,可以去搜讲解更详细的题解)。

(这个本质好像就是求最大基环树生成森林)

代码十分简单

code

t2
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, r, c;
int fa[N << 1], res[N << 1];
struct node
{
    int x, y, val;
} e[N];

inline bool cmp(node a, node b) { return a.val > b.val; }

inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); };

inline bool merge(int x, int y)
{
    x = find(x), y = find(y);
    if (x == y)
        return 1;
    fa[x] = y;
    res[y] += res[x];
    return 0;
}

signed main()
{
    freopen("oblivious.in", "r", stdin);
    freopen("oblivious.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> r >> c;
    for (int i = 1; i <= r + c; ++i)
        fa[i] = i, res[i] = 1;
    for (int i = 1; i <= n; ++i)
        cin >> e[i].x >> e[i].y >> e[i].val, e[i].y += r;
    sort(e + 1, e + 1 + n, cmp);
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (!merge(e[i].x, e[i].y))
        {
            int x = find(e[i].x);
            if (res[x])
                ans += e[i].val, --res[find(x)];
        }
        else
        {
            int x = find(e[i].x);
            if (res[x])
                --res[x], ans += e[i].val;
        }
    }
    cout << ans;
    return 0;
}

t3

困难题(我是蒟蒻)。

首先对于区间加可以转化成差分序列上单点操作。

然后我们考虑状压。

感觉我不太会表达啊。

直接看代码?

代码里有注释。

code

t3
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 25;
const int V = 1 << 22;
const int mod = 1e9 + 7;
int n;
int a[N], b[N], jc[N], val[N];
int dp[V], g[V], siz[V];
bitset<V> vis, flag;

inline int km(int a, int b)
{
    if (b < 0)
        return 0;
    int ans = 1;
    while (b)
    {
        if (b & 1)
            (ans *= a) %= mod;
        (a *= a) %= mod;
        b >>= 1;
    }
    return ans;
}

signed main()
{
    freopen("array.in", "r", stdin);
    freopen("array.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;

    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    --n;

    jc[0] = val[0] = 1;
    for (int i = 1; i <= n; ++i)
        jc[i] = jc[i - 1] * i % mod;
    for (int i = 1; i <= n + 1; ++i) //???
        val[i] = 2 * km(i + 2, i - 1) % mod;

    int top = 0;
    for (int i = 1; i <= n; ++i)
        if (a[i + 1] - a[i])
            b[++top] = a[i + 1] - a[i];
    n = top;
    // cerr << "n=" << n << "\n";
    for (int i = 1; i < (1 << n); ++i) // S
    {
        int sum = 0;
        for (int j = 0; j < n; ++j) // 该状态下之和
            if (1 & (i >> j))
                sum += b[j + 1], ++siz[i];
        if (!sum)
        {
            dp[i] = 1;
            if (siz[i] == 1)
                g[i] = 1;
            else
                g[i] = km(siz[i], siz[i] - 2);
        }
    }

    for (int i = 1; i < (1 << n); ++i) // S
    {
        if (!dp[i])
            continue;
        bool bj = 1;
        for (int j = (i - 1) & i; j; j = (j - 1) & i) // 枚举子集
            if (dp[j])
            {
                bj = 0;
                break;
            }
        flag[i] = bj; // 子集中是否可拆出sum和为0 yes:0 no:1
    }

    for (int i = 1; i < (1 << n); ++i) // S
    {
        if (!dp[i])
            continue;
        vis = 0; // 标记是否计算过
        for (int j = (i - 1) & i; j; j = (j - 1) & i)
        {
            if (flag[j] && !vis[j])
            {
                if (dp[i] < dp[j] + dp[i ^ j]) // j:子集 i^j:子集的补集
                    dp[i] = dp[j] + dp[i ^ j], g[i] = g[j] * g[i ^ j] % mod;
                else if (dp[i] == dp[j] + dp[i ^ j])
                    (g[i] += g[j] * g[i ^ j] % mod) %= mod;
                for (int k = i ^ j; k; k = (k - 1) & (i ^ j))
                    vis[k] = 1;
            }
        }
    }

    int ans = 0, cnt = 0;
    for (int i = 0; i < (1 << n); ++i)
        ans = max(ans, dp[i]);

    ans = n - ans;

    if (ans == n)
        cnt = 2 * km(n + 2, n - 1) % mod * jc[n] % mod;
    else
    {
        for (int i = 0; i < (1 << n); ++i)
        {
            if (dp[i] == n - ans)
                (cnt += g[i] * val[n - siz[i]]) %= mod;
        }
        (cnt *= jc[ans]) %= mod;
    }

    cout << ans << "\n"
         << max(cnt, 1ll) << "\n";
    return 0;
}

t4

数学黑题?

我是数学低手wwwww

posted @ 2025-11-16 21:17  HS_fu3  阅读(15)  评论(0)    收藏  举报