03/10/2024 上课笔记 & 解题报告

双向链表

前言

第一次接触这玩意儿,所以记录一下。

题目

[国家集训队] 种树

题目描述

A城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。

园林部门得到指令后,初步规划出 \(n\) 个种树的位置,顺时针编号 \(1\)\(n\)。并且每个位置都有一个美观度 \(A_i\),如果在这里种树就可以得到这 \(A_i\) 的美观度。但由于 \(A\) 城市土壤肥力欠佳,两棵树决不能种在相邻的位置(\(i\) 号位置和 \(i+1\) 号位置叫相邻位置。值得注意的是 \(1\) 号和 \(n\) 号也算相邻位置)。

最终市政府给园林部门提供了 \(m\) 棵树苗并要求全部种上,请你帮忙设计种树方案使得美观度总和最大。如果无法将 \(m\) 棵树苗全部种上,给出无解信息。

输入格式

输入的第一行包含两个正整数 \(n\)\(m\)

第二行 \(n\) 个整数,第 \(i\) 个代表 \(A_i\)

输出格式

输出一个整数,表示最佳植树方案可以得到的美观度。如果无解输出 Error!

样例 #1

样例输入 #1
7 3
1 2 3 4 5 6 7
样例输出 #1
15

样例 #2

样例输入 #2
7 4
1 2 3 4 5 6 7
样例输出 #2
Error!

提示

数据编号 \(n\) 的大小 数据编号 \(n\) 的大小
\(1\) \(30\) \(11\) \(200\)
\(2\) \(35\) \(12\) \(2007\)
\(3\) \(40\) \(13\) \(2008\)
\(4\) \(45\) \(14\) \(2009\)
\(5\) \(50\) \(15\) \(2010\)
\(6\) \(55\) \(16\) \(2011\)
\(7\) \(60\) \(17\) \(2012\)
\(8\) \(65\) \(18\) \(199999\)
\(9\) \(200\) \(19\) \(199999\)
\(10\) \(200\) \(20\) \(200000\)

对于全部数据:\(m\le n\)\(-1000\le A_i\le1000\)

题目分析。

这是一道可反悔贪心 + 双向链表的题。

  • 一、可反悔贪心
    题目中说,一个树选了旁边两个数就不能选,比如下图。
    image

我们肯定先选 \(9\),然后 \(8\)\(8\) 不能选,所以最后答案是 \(9+1=10\)。但是这题最优解显然是 \(8+8=16\)

我们肯定会想到用可反悔贪心,用大根堆。

但是我们无法比较,来进行反悔。

于是我们想了一个办法。第一次选 \(9\) 时将 \(8+8-9=7\) 加入这个大根堆。我们不妨来模拟一下。

首先 \(ans\)\(9\),然后删去 \(8\)\(8\)\(7\) 于队列。
image

后出 \(8\),被删了,跳过,然后就弹出 \(7\) 了,\(ans\) 加上 \(7\),把两边剔除,

image

于是答案就是 \(9+7=16\),我们发现和 \(8+8=16\) 结果一样。

  • 二、如何用双向链表删点。

image

中间的为 \(x\),我们只要让 [p[x].lft].rgt = p[x].rgt,[x].rgt].lft = p[x].lft

然后分别令 \(x\)p[x].lft 和 p[x].rgt` 就行了。

代码

/*
    Author: Rainypaster(lhy)
    Time: 03/10/2024
    File: P1972.cpp
    Email: 2795974905@qq.com
*/
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <bits/stdc++.h>
#define int long long
using namespace std;

namespace IO
{
    template<typename T>
    T read(T x)
    {
        T sum = 0, opt = 1;
        char ch = getchar();
        while(!isdigit(ch)) opt = (ch == '-') ? -1 : 1, ch = getchar();
        while( isdigit(ch)) sum = (sum << 1) + (sum << 3) + (ch ^ 48), ch = getchar();
        return sum * opt;
    }

}
#define read IO::read(0)
#define rep(i, n) for(int i = 1; i <= n; i ++)
#define repa(i, n) for(int i = 0; i < n; i ++)
#define repb(i, n) for(int i = 1; i <= n; i ++)
#define repc(i, n) for(int i = 0; i < n; i ++)
#define lson (u << 1)
#define rson (u << 1 | 1)
#define gcd(a, b) __gcd(a, b)

const int N = 2e5 + 5;

struct Node{
	int val,id;
	bool operator <(Node it) const{
		return val < it.val;
	}
};

priority_queue<Node> q;
struct node
{
    int val, lft, rgt;
}p[N];

bool vis[N];

int ans;

void delt(int x)
{
    p[p[x].lft].rgt = p[x].rgt;
    p[p[x].rgt].lft = p[x].lft;
}

void solve()
{
    int n = read, m = read;
    if(n / 2 < m) {
        puts("Error!");
        return ;
    }
    for(int i = 1;i <= n;i ++ ){
        p[i].val = read;
        p[i].lft = i - 1;
        p[i].rgt = i + 1;
        q.push({p[i].val, i});
    }
    p[1].lft = n, p[n].rgt = 1;

    for(int i = 1;i <= m;i ++ ){
        while(vis[q.top().id]) q.pop();
        Node now = q.top();
        q.pop();
        vis[p[now.id].lft] = vis[p[now.id].rgt] = true;
        p[now.id].val = p[p[now.id].lft].val + p[p[now.id].rgt].val - p[now.id].val;
        ans += now.val;
        q.push({p[now.id].val, now.id});
        delt(p[now.id].lft), delt(p[now.id].rgt);
    }
    cout << ans << endl;
}

signed main()
{
    int T = 1;
    while(T -- ) solve();
    return 0;
}

根号分治

前言

简单讲一下,比较简单。

题目

哈希冲突

题目背景

众所周知,模数的 hash 会产生冲突。例如,如果模的数 \(p=7\),那么 \(4\)\(11\) 便冲突了。

题目描述

B 君对 hash 冲突很感兴趣。他会给出一个正整数序列 \(\text{value}\)

自然,B 君会把这些数据存进 hash 池。第 \(\text{value}_k\) 会被存进 \((k \bmod p)\) 这个池。这样就能造成很多冲突。

B 君会给定许多个 \(p\)\(x\),询问在模 \(p\) 时,\(x\) 这个池内 数的总和

另外,B 君会随时更改 \(\text{value}_k\)。每次更改立即生效。

保证 \({1\leq p<n}\)

输入格式

第一行,两个正整数 \(n\), \(m\),其中 \(n\) 代表序列长度,\(m\) 代表 B 君的操作次数。

第一行,\(n\) 个正整数,代表初始序列。

接下来 \(m\) 行,首先是一个字符 \(\text{cmd}\),然后是两个整数 \(x,y\)

  • \(\text{cmd}=\text{A}\),则询问在模 \(x\) 时,\(y\) 池内 数的总和

  • \(\text{cmd}=\text{C}\),则将 \(\text{value}_x\) 修改为 \(y\)

输出格式

对于每个询问输出一个正整数,进行回答。

样例 #1

样例输入 #1

10 5
1 2 3 4 5 6 7 8 9 10
A 2 1
C 1 20
A 3 1
C 5 1
A 5 0

样例输出 #1

25
41
11

提示

样例解释

A 2 1 的答案是 1+3+5+7+9=25

A 3 1 的答案是 20+4+7+10=41

A 5 0 的答案是 1+10=11

数据规模

对于 \(10\%\)的数据,有 \(n\leq 1000\)\(m\leq 1000\)

对于 \(60\%\) 的数据,有 \(n\leq 100000\)\(m\leq 100000\)

对于 \(100\%\) 的数据,有 \(n\leq 150000\)\(m\leq 150000\)

保证所有数据合法,且 \(1\leq \mathrm{value}_i \leq 1000\)

分析

我们很容易发现,% 的数越小越不好操作,相反,越大的数暴力就能通过。

所以我们不如把 % 的小于 \(\sqrt(n)\) 的数用 \(f_{x, y}\) 表示 \(x\) % 结果为 \(y\) 的数字的和。

时间复杂度为 \(O(n\sqrt(n))\)。比较优秀。

更改只要更改对应 % 的数组的值就行了。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 150005;

int f[450][450];
int a[N];
signed main()
{
    int n, m;
    cin >> n >> m;

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

    for(int i = 1;i <= n;i ++ ){
        for(int j = 1;j <= sqrt(n);j ++ ) f[j][i % j] += a[i];
    }

    while(m -- ){
        char cmd;
        cin >> cmd;
        int x, y;
        cin >> x >> y;

        if(cmd == 'A'){
            if(x <= sqrt(n)){
                cout << f[x][y] << endl;
            }
            else{
                int ans = 0;
                for(int i = y;i <= n;i += x){
                    ans += a[i];
                }
                cout << ans << endl;
            }
        }
        else{
            for(int i = 1;i <= sqrt(n);i ++ ) f[i	][x % i] += y - a[x];
            a[x] = y;
        }
    }
    return 0;
}
posted @ 2024-03-10 22:31  浮光流年  阅读(7)  评论(0编辑  收藏  举报