上午
內容:基礎資料結構
1. 連結串列
分類:單向和雙向
單向:當前連結串列只指向下一個元素
雙向:對於每個元素,記錄其前面一個元素,也記錄其後面一個元素。
注意:連結串列不建議使用 STL 的某些元素進行替代,手寫連結串列更為方便。
1. 單向連結串列
做法:維護每個元素編號,然後維護 nx 指標,表示當前元素的下一個元素是誰加入和刪除都是方便的。
手寫的單向連結串列:
void ins(int x,int y){
int to=nx[x];
nx[x]=y,nx[y]=to;
}
void erase(int x){
int to=nx[x];
nx[x]=nx[to];
}
T1 單向連結串列
問題簡述:如題
思路:我諤諤, 直接模擬
std:
#include <iostream>
using namespace std;
const int MAXN = 1e6 + 10;
int nx[MAXN];
int main(){
int q;
cin >> q;
for(int i = 1;i <= q;i++){
int op;
cin >> op;
if(op == 1){
int x, y;
cin >> x >> y;
int a;
a = nx[x];
nx[x] = y;
nx[y] = a;
}else if(op == 2){
int x;
cin >> x;
cout << nx[x] << '\n';
}else{
int x;
cin >> x;
int a = nx[x];
nx[x] = nx[a];
}
}
return 0;
}
記錄
前項星
本質:多個單項鍊表組成,維護鏈頭陣列,然後可以支援每個點加邊。
struct nod{
int next,to;
}e[xx*2];
int cnt,h[xx];
void add(int x,int y){
cnt++;
e[cnt]={h[x],y};
h[x]=cnt;
}
void dfs(int x,int y){
for(int i=h[x];i;i=e[i].next){
dfs(e[i].to,x);
}
}
T2 單向連結串列
思路:直接複製上述程式
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100005;
struct Node{
int next,to;
}e[MAXN*2];
int n,m,cnt,h[MAXN];
void add(int x,int y){
cnt++;
e[cnt]={h[x],y};
h[x]=cnt;
}
int ans[MAXN],id;
void dfs(int x){
if(!ans[x])ans[x]=id;
else return;
for(int i=h[x];i;i=e[i].next)dfs(e[i].to);
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
add(b,a);
}
for(int i=n;i>=1;i--)id=i,dfs(i);
for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
return 0;
}
記錄
2. 雙向連結串列
做法:每個元素維護前驅 \(\text{pr}\) 和後繼 \(\text{nx}\) 兩個陣列,可以實現動態增刪的操作
手寫的雙向連結串列:
void ins(int x,int y){
int to=nx[x];
nx[x]=y,pr[to]=y;
pr[y]=x,nx[y]=to;
}
void era(int x){
int L=pr[x],R=nx[x];
nx[L]=R,pr[R]=L;
}
2. 棧
做法:維護一個序列,每次從末端加入元素,然後末端彈出元素
具體維護:使用一個陣列,維護一個 \(\text{tp}\) 指標進行處理
手寫的棧:
int stk[xx],tp;
void push(int x){
stk[++tp]=x;
}
int top(){
return stk[tp];
}
void pop(){
--tp;
}
STL 的棧:
stack<int>stk;
stk.push(1);
stk.pop();
cout<<stk.top()<<"\n";
cout<<stk.size()<<"\n";
T3 棧
模板題直接用 STL。
#include <iostream>
#include <stack>
#include <cstring>
using namespace std;
#define int unsigned long long
stack<int>stk;
void a(int n){
for(int i = 1;i <= n;i++){
string op;
cin >> op;
if(op == "push"){
int x;
cin >> x;
stk.push(x);
} else if(op == "pop"){
if(!stk.empty()){
stk.pop();
} else {
cout << "Empty" << '\n';
}
} else if(op == "query"){
if(!stk.empty()){
cout << stk.top() << '\n';
} else {
cout << "Anguei!" << '\n';
}
} else if(op == "size"){
cout << stk.size() << '\n';
}
}
}
int t, n;
signed main(){
cin >> t;
for(int i = 1;i <= t;i++){
int n;
cin >> n;
a(n);
while(!stk.empty()){
stk.pop();
}
}
/*
stack<int>stk;
stk.top();
stk.pop();
cout << stk.top() << '\n';
cout << stk.size() << '\n';
*/
return 0;
}
記錄
單調棧
實質:用棧維護了單調的結構
T3 棧
問題簡述:給定一個長度為 \(n\) 的數列 \(a_i\)。然後,對於每個元素,找到在這個元素之後,第一個大於當前元素的下標。
方法:從後往前列舉,棧內維護單調的下標,滿足這些下標的值是遞減的
關鍵:保留對當前狀態最優的資訊。
#include<bits/stdc++.h>
using namespace std;
int n,a[3000005];
int top,ans[3000005],maxn;
struct node{
int val,i;
};
stack<node>sta;
stack<int>p;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=n;i>=1;i--){
int vis=0;
while(!sta.empty()){
if(a[i]<sta.top().val){
p.push(sta.top().i);
vis=1;
break;
}
else sta.pop();
}
if(!vis)p.push(0);
sta.push((node){a[i],i});
}
for(int i=1;i<=n;i++){
cout<<p.top()<<' ';
p.pop();
}
}
記錄
3. 佇列
方法:後進先出
手寫的佇列:
int q[xx],l,r;
void push(int x){
q[++r]=x;
}
int front(){
return q[l];
}
void pop(){
l++;
}
STL 佇列:
queue<int>q;
q.push(1);
q.pop();
cout<<q.front()<<"\n";
cout<<q.size()<<"\n";
T4 佇列
模板題直接用 STL。
#include <iostream>
#include <queue>
using namespace std;
queue<int> q;
int main(){
int n;
cin >> n;
for(int i = 1;i <= n;i++){
int op;
cin >> op;
if(op == 1){
int x;
cin >> x;
q.push(x);
} else if(op == 2){
if(!q.empty()){
q.pop();
} else {
cout << "ERR_CANNOT_POP" << '\n';
}
} else if(op == 3){
if(!q.empty()){
cout << q.front() << '\n';
} else {
cout << "ERR_CANNOT_QUERY" << '\n';
}
} else {
cout << q.size() << '\n';
}
}
return 0;
}
單調佇列
定義:權值是遞減的(求目前最大值),但是 \(\text{id}\) 是遞增的(有 \(\text{id}\) 大於某個位置的限制),有單調性質。
關鍵:保留對於當前而言更優的一個資訊。
T5 單調佇列
方法:
-
判斷隊首元素是否超出範圍,如果是,則隊首元素出隊;
-
判斷隊尾元素是否滿足要求(例如求區間最大值,要求則為隊尾元素小於插入的元素),如果是,則隊尾元素出隊,返回步驟 \(2\);如果不是,則進入下一步;
-
將元素插入隊尾。
std:
#include <bits/stdc++.h>
using namespace std;
int a[1000005];
int q[1000005];
int l, r;
int read() {
char c;
int w = 1;
while ((c = getchar()) > '9' || c < '0')
if (c == '-')
w = -1;
int ans = c - '0';
while ((c = getchar()) >= '0' && c <= '9')
ans = (ans << 1) + (ans << 3) + c - '0';
return ans * w;
}
int m;
int main() {
int n;
n = read();
m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
l = r = 1;
q[1] = 1;
for (int i = 1; i <= n + 1; i++) {
while (l <= r && q[l] < i - m)
l++;
if (i > m)
printf("%d ", a[q[l]]);
while (l <= r && a[q[r]] >= a[i])
r--;
q[++r] = i;
}
l = r = 1;
q[1] = 1;
cout << endl;
for (int i = 1; i <= n + 1; i++) {
while (l <= r && q[l] < i - m)
l++;
if (i > m)
printf("%d ", a[q[l]]);
while (l <= r && a[q[r]] <= a[i])
r--;
q[++r] = i;
}
return 0;
}
記錄
4. 佇列
方法:維護一個序列,每次加入一個元素,詢問當前序列中最小的元素,然後刪除最小元素。
具體維護:一般維護的稱為二叉堆,即維護一個結構,支援上述處理過程。
每個節點 \(i\) 儲存一個權值,左兒子是 \(2 \cdot i\),右兒子是 \(2 \cdot i + 1\)。
手寫的堆:
int hp[xx],sz;
void push(int val){
int id=++sz;
hp[id]=val;
while(id!=1){
int to=id/2;
if(hp[id]<hp[to])swap(hp[id],hp[to]);
id=to;
}
}
void pop(){
hp[1]=hp[sz],--sz;
int id=1;
while((id*2)<=sz){
int L=id*2,R=id*2+1,ty=L;
if(R<=sz&&hp[R]<hp[ty])ty=R;
if(hp[ty]<hp[id])swap(hp[ty],hp[id])
id=ty;
}
}
STL 堆:
priority_queue<int>q;//大根堆!
q.push(1),q.pop();
cout<<g.top()<<"\n";
cout<<q,size()<<\n";
priority_queue<int,vector<int>,greater<int>>q;//小根堆!
q.push(1),q.pop();
cout<<g.top()<<"\n";
cout<<q.size()<<"\n";
T5 堆
思路:模板是小根堆,直接用STL
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int MAXN=1000005;
priority_queue<int,vector<int>,less<int> > h;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int op;
cin>>op;
if(op==1){
int x;
cin>>x;
h.push(-x);
}else if(op==2)cout<<-h.top()<<'\n';
else h.pop();
}
return 0;
}
記錄
對頂堆
T6 堆
問題簡述:一個序列,我們每次加入一個元素,或者進行詢問。
維護一個初始指標 \(i=0\),每次詢問的時候將 \(i=i+1\) 然後詢問第 \(i\) 小的值是多少。
思路:使用兩個堆,把序列分為前半和後半,分界點位置就是我們嘗試輸出的值。
std:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200010;
priority_queue<int>q1, q2;
int a[MAXN], u[MAXN];
int main(){
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
cin>>u[i];
for(int l=u[i-1]+1;l<=u[i];l++){
q2.push(a[l]);
}
while(q1.size()&&q2.size()&&-q1.top()<q2.top()){
q1.push(-q2.top());
q2.pop();
}
while(q2.size()<i){
q2.push(-q1.top());
q1.pop();
}
while(q2.size()>i){
q1.push(-q2.top());
q2.pop();
}
cout<<q2.top()<<endl;
}
return 0;
}
記錄
5. 哈夫曼樹
本質:在堆的基礎上的一點點貪心的擴充套件
性質:
每個數貢獻的次數是他到根的邊數。
數大的貢獻較少,即經過的邊數較少。
如果把邊順次標號為 \(0, 1\),則每個葉子到根的一個字串稱為哈夫曼編碼。
T7 合併果子
問題簡述:有 \(n\) 堆果子,第 \(i\) 堆初始為 \(a_i\) 大,然後你需要做 \(n − 1\) 次操作,
每次選擇兩堆果子,將其合併為一堆,
即刪除原來兩堆 \(i, j\) 變成新堆 \(a_i + a_j\),並消耗新堆大小的體力,問:最少消耗體力。
std:
#include <iostream>
#include <queue>
using namespace std;
int n, x, ans;
priority_queue<int, vector<int>, greater<int> >q;
int main(){
cin >> n;
for(int i = 1;i <= n;i++){
cin >> x;
q.push(x);
}
while(q.size() >= 2){
int a = q.top();
q.pop();
int b = q.top();
q.pop();
ans += a + b;
q.push(a + b);
}
cout << ans << endl;
return 0;
}
記錄
哈夫曼樹擴充套件
思路:選擇最小的兩堆合併,並一直執行這個過程
多叉的哈夫曼樹同樣涉及到一個補 \(0\) 的貪心思想。
具體的,補其 \(0\) 使得 \(n − 1 \bmod (k − 1) = 0\)。
T8 荷馬史詩
問題簡述:給定 \(n\) 個單詞的出現次數,請你用字符集大小為 \(k\) 的字串來替換每個單詞,使得每個單詞的出現次數乘對應字串的長度最小,你需要滿足要求:對於任意兩個單詞對應的字串不應該有字首包含關係,比如 \(\texttt{abbc}\) 字首包含 \(\texttt{abb}\)。
思路:注意到,沒有字首包含關係完全對應了哈夫曼編碼,而我們最最佳化次數正好是哈夫曼樹的最小的貢獻!
所以我們建立擴充套件的 \(k\) 叉哈夫曼樹即可得到答案。
std:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read()
{
char c;
int w=1;
while((c=getchar())>'9'||c<'0')if(c=='-')w=-1;
int ans=c-'0';
while((c=getchar())>='0'&&c<='9')ans=(ans<<1)+(ans<<3)+c-'0';
return ans*w;
}
priority_queue<pair<int,int> >q;
int a[5000005];
int n;
int k;
signed main(){
n=read();
k=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
q.push(make_pair(-a[i],-1));
}
while((n-1)%(k-1))n++,q.push(make_pair(0,-1));
int anss=0;
for(int i=1;i<=(n-1)/(k-1);i++)
{
int ans=0;
int maxx=0;
for(int j=1;j<=k;j++)
{
ans+=(-q.top().first);
maxx=max(maxx,-q.top().second);
q.pop();
}
anss+=ans;
q.push(make_pair(-ans,-maxx-1));
}
cout<<anss<<endl<<-q.top().second-1<<endl;
return 0;
}
記錄