e題: early orders
題意:給定 n ,k 和長度為n的陣列x[i]
1<=k<=n<=2e5, 1<=x[i]<=k;
要在這個陣列裡找乙個長度為k的子串行,使得數字1到k在這個序列裡各出現一次,輸出字典序最小的子串行
輸入保證陣列 x[i] 包括了1到k的所有數字
思路:很容易想到貪心地選擇小的數放在前面,可以用單調棧來做
做法:從左往右掃原陣列,對於x[i]:
1、棧為空時,數字入棧;
2、棧非空,那麼可以把棧裡大於x[i]的數彈出,(從棧頂依次往下彈出) ,這樣就維護了乙個單調棧;
3、棧裡存在數字x[i],直接continue,就算棧裡有比x[i]大的數也不彈出;(可以試試1 2 1 3 2這個樣例,1不能把2彈出,否則1 2 3變成1 3 2)
4、棧裡的某個數字在x[i+1]到x[n]都不存在,那麼這個數就算比 x[i] 大,也不彈出這個數,就是說從棧頂彈出到這種數就不繼續彈了
注釋**:
#include#include#includeusing namespace std;如果覺的自己的**沒問題,但就是wa了,歡迎找我幫忙造反例const int maxn = 2e5 + 7;
int a[maxn];
int last[maxn];
bool vis[maxn];
int ans[maxn];
int st[maxn];//手寫棧
int main()
} for (int i = 1; i <= k; i++) vis[i] = false;
for (int i = 1; i <= n; i++)
st[++r] = a[i];//入棧操作
vis[a[i]] = true;//在棧裡面,vis置為true
} printf("%d", st[1]);
for (int i = 2; i <= k; i++) printf(" %d", st[i]);
printf("\n");
return 0;
}
i題:full depth morning show
題意:給定一顆節點數為n的樹,給定每個節點的點權 t[ i ] ,給定 n - 1條邊(u,v,w),w為這條邊的邊權,用 f[u][v]表示從u到v的這條路徑的花費
題目定義f[u][v] 為u到v的路徑中所有邊的邊權之和乘以u,v的點權之和,用公式來表示就是f[u][v] = (σw)* (t[ u ] + t[ v ])
總花費為所有從 非根節點 到 根節點 的路徑的花費之和,即計算σf[u][root] (u!=root)
在第一行輸出以一號節點為根節點時,總共的花費
在第二行輸出以二號節點為根節點時,總共的花費
一共輸出n行
思路:所有節點都要體驗一遍當根節點的情況,那麼可以考慮用換根dp,記dp[i]為以 i 節點為根節點的總貢獻
怎麼計算總花費,我們可以換個角度來計算,不把它看成是所有路徑的貢獻之和,而看成是所有邊的貢獻之和
考慮以一號節點為根節點,任選一條邊,它的貢獻怎麼算,用 g[i] 表示一條邊的貢獻
如圖:
這樣我們得到了任意一條邊的貢獻為:g[ i ] = (t_sum[子樹] + size[子樹] * t[root] ) * w[ i ]
把所有邊的貢獻加起來就是總貢獻:dp[root] = σg[i]
我們一開始可以把這棵樹以1號節點為根節點,先掃一遍,計算出分別以各個節點為根節點的子樹,它的大小size[子樹],它的t_sum[子樹]
然後再掃一遍,計算出以1號節點為根節點時,用計算出所有邊的貢獻並加起來的方法計算出總貢獻
這兩次掃瞄可以一起掃瞄
**:
void dfs1(int s, int f)for (int i = head[s]; i; i = edge[i].next)
}
dp[1] += su * t[1];這樣我們得到了dp[1],現在考慮dp陣列怎麼轉移
dp[1] = σg[i] = σ( (t_sum[子樹] + size[子樹] * t[1] ) * w[ i ] )
設2號節點為1號節點的兒子節點
現把根節點從1號節點轉移到2號節點
如圖:
只有兩個節點的t_sum[子樹]和size[子樹]改變了,同時對比以下這兩項
dp[1] = σg[i] = σ( (t_sum[子樹] + size[子樹] * t[1] ) * w[ i ] )
dp[2] = σg[i] = σ( (t_sum[子樹] + size[子樹] * t[2] ) * w[ i ] )
由於只有兩個節點的t_sum[子樹]發生了變化,而所有size[子樹]乘的t[1]變成了t[2]
所以我們把邊的貢獻拆成兩項g[i] =t_sum[子樹] * w[i] (第一項) + size[子樹]*t[2]*w[i](第二項)
可以發現,只有一條邊的貢獻的第一項發生了變化,而所有邊的貢獻的第二項發生了變化,但是第二項裡的size[子樹]*w[i]也是只有一條邊發生了變化
如圖:
所以我們要分別來計算這兩項的改變量
計算第一項的改變量,我們可以把改變了的那條邊的第一項的改變量求出來
計算第二項的改變量,我們可以用su[i]表示以 i 為根節點時, σsize[子樹]*w[i]為多少:su[i] = σsize[子樹]*w[i]。然後就好算了
能算出這兩項改變量,就可以用dfs掃一遍來在樹上算dp[i]了
dfs**:
void dfs2(int s)總的**:}
#include#include#includeusing namespace std;有許多樹上的題都是這種二次dfs掃瞄,考慮換根後的改變量const int maxn = 2e5 + 7;
struct edge edge[maxn*2];
int head[maxn], tot = 0;
void add(int u, int v, long long w)
long long dp[maxn],t[maxn],t_sum[maxn],su[maxn];
int siz[maxn], fa[maxn];
void dfs1(int s, int f)
for (int i = head[s]; i; i = edge[i].next)
}void dfs2(int s)
}int main()
int u, v; long long w;
for (int i = 1; i <= n - 1; i++)
dfs1(1, 0);
dp[1] += su[1] * t[1];
dfs2(1);
for (int i = 1; i <= n; i++)
return 0;
}
牛客2023年度訓練聯盟熱身訓練賽第一場E題
知識點 單調棧 ac include include include using namespace std int a 200005 int vis 200010 int last 200005 int s 200005 int top intmain for int i 1 i k i for ...
2023年度訓練聯盟熱身訓練賽第三場(IJ)
傳送門 題意 你有一輛輪胎會跑氣的自行車,打滿一次氣可以走的距離是d,一開始自行車的氣是滿的,城市中有 n 個點,你要從1點到 n 點,其中有 t 個點處可以給自行車打氣,你只能在自行車有氣的時候走,問從1到 n 的最短路 思路 先跑一遍floyd求出每兩點之間的最短路,然後重新構圖,兩個打氣點之間...
2023年度訓練聯盟熱身訓練賽第一場
鏈結 題意 給定若干個空間中的點,求從三個標準空間直角座標平面進去能夠全部覆蓋這些點的最小半徑 題解 注意 includeusing namespace std define n 1e5 5 define inf 99999999 define eps 1e 6 define powe x x x ...