03/10/2024 上課筆記 & 解題報告

浮光流年發表於2024-03-10

雙向連結串列

前言

第一次接觸這玩意兒,所以記錄一下。

題目

[國家集訓隊] 種樹

題目描述

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;
}

相關文章