雙向連結串列
前言
第一次接觸這玩意兒,所以記錄一下。
題目
[國家集訓隊] 種樹
題目描述
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\)。
題目分析。
這是一道可反悔貪心 + 雙向連結串列的題。
- 一、可反悔貪心
題目中說,一個樹選了旁邊兩個數就不能選,比如下圖。
我們肯定先選 \(9\),然後 \(8\)、\(8\) 不能選,所以最後答案是 \(9+1=10\)。但是這題最優解顯然是 \(8+8=16\)。
我們肯定會想到用可反悔貪心,用大根堆。
但是我們無法比較,來進行反悔。
於是我們想了一個辦法。第一次選 \(9\) 時將 \(8+8-9=7\) 加入這個大根堆。我們不妨來模擬一下。
首先 \(ans\) 加 \(9\),然後刪去 \(8\)、\(8\), \(7\) 於佇列。
後出 \(8\),被刪了,跳過,然後就彈出 \(7\) 了,\(ans\) 加上 \(7\),把兩邊剔除,
於是答案就是 \(9+7=16\),我們發現和 \(8+8=16\) 結果一樣。
- 二、如何用雙向連結串列刪點。
中間的為 \(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;
}