用棧操作構建數組 | 形成兩個異或相等數組的三元組數目 |
收集樹上所有蘋果的最少時間 | 切披薩的方案數 |
---|---|---|---|
3分 簡單 | 4分 中等 | 5分 中等 | 7分 困難 |
1441 用棧操作構建數組
枚舉i,從1-n,用st記錄當前匹配到目標數組的哪個位置
如果當前i==target[st],表示匹配,進行Push操作
否則,表示不匹配,先Push,Pop,這樣就不產生影響
class Solution { public: vector<string> buildArray(vector<int>& target, int n) { vector<string>ans; int st=0; int len=target.size(); for(int i=1;i<=n;i++){ if(i==target[st]){ ans.push_back("Push"); st++; } else{ ans.push_back("Push"); ans.push_back("Pop"); } if(st==len) break; } return ans; } };
1442 形成兩個異或相等數組的三元組數目
這題複雜度O(N^2)的話,就很簡單,符合這道題的定位,要優化到O(n)的話還是有點困難的。
-
O(N^2)的做法
要使(i~j-1這一段連續異或結果)等於(j~k這一段連續異或結果)
我們可以枚舉分界點j,然後用map記錄以j爲右端點的所有子段異或情況(代碼中,用i表示分界點)
for(int j=i-1;j>=0;j--){ tmp^=arr[j]; mp[tmp]++; }
然後再枚舉以i爲左端點的情況,如果此時右端點爲j,i-j的異或結果爲tmp,那麼這部分的貢獻就是mp[tmp]
for(int j=i;j<n;j++){ tmp^=arr[j]; ans+=mp[tmp]; }
class Solution { public: int countTriplets(vector<int>& arr) { int n=arr.size(); map<int,int>mp; int ans=0; for(int i=1;i<n;i++){ int tmp=0; mp.clear(); for(int j=i-1;j>=0;j--){ tmp^=arr[j]; mp[tmp]++; } tmp=0; for(int j=i;j<n;j++){ tmp^=arr[j]; ans+=mp[tmp]; } } return ans; } };
-
O(N)的做法
三元組表示其實就是一段區間
需要轉化下題目信息
根據題目的定義
a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1]
b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k]
當a==b成立時
能得出一個條件a^b=0; 式子(1)
再觀察
x=arr[1]^arr[2]^...^arr[i]
y=arr[1]^arr[2]^...^arr[i]^arr[i+1]^...^arr[j]
若x==y,則arr[i+1]^...^arr[j]=0(一個數只有異或上0纔等於本身)
回頭看式子(1),i+1其實就是a的左端,j就是b的右端
對應三元組(i,j,k)裏的i和k
所以我們可以用map來記錄累積異或值相同時的下標,然後對於三元組(i,j,k),j可以取遍 i+1,⋯,k-1,k 共 k−i種情況。
得到初步的N^2代碼
for(int i=0;i<index.size()-1;i++) { for(int j=i+1;j<index.size()-1;j++) ans += index[j]-(index[i]+1); }
index表示相同累計異或值的下標
兩層for循環枚舉三元組的左右端點i和k(代碼中變量設置成了i和j)
到這步,優化到O(n)就很容易了
對於每個i,需要減去(n-i-1)個(index[i]+1)
需要加上index[i+1],index[i+2]...index[n],這一部分用前綴和求一下即可
這樣,就把第二個for循環優化掉了
class Solution { public: int countTriplets(vector<int>& arr) { map<int,vector<int>>mp; mp[0].push_back(0); int tmp=0; for(int i=0;i<arr.size();i++){ tmp^=arr[i]; mp[tmp].push_back(i+1); } int ans=0; for(auto it:mp){ vector<int>&index=it.second; vector<int>sum(index); int n=index.size(); for(int i=1;i<n;i++) sum[i]+=sum[i-1]; for(int i=0;i<n;i++){ ans+=sum[n-1]-sum[i]-(n-i-1)*(index[i]+1); } } return ans; } };
1443 收集樹上所有蘋果的最少時間
很難直接獨立求出收集每個蘋果的時間,收集完這顆蘋果,選擇的下一個蘋果不同,時間可能不同。
那麼,不能直接求,我們可以考慮每條邊被經過了幾次。
先dfs求出以0爲根,每個節點的高度dep[u]
先假設每個蘋果都是從根開始過來收集的,那麼收集每個蘋果的時間就是dep[u]*2(根->蘋果,蘋果->根)
對於每個節點u,和它的一個兒子節點v,u-v邊記爲x
如果v的子樹裏只有一個蘋果節點,那麼x這條邊確實經過了兩次,不需要進行處理;
如果v的子樹裏有兩個蘋果節點A和B,先到A,那麼A肯定是先到B,B再經過x邊回到根,該情況下x邊就多加了兩次(來一次,去一次)。
類推,對於子樹有多個蘋果節點,需要減去
max(0,(num[v]-1))*2;(num[v]是v子樹的蘋果節點個數)
const int maxn=100050; class Solution { public: vector<int>son[maxn]; int dep[maxn],num[maxn],ans; void dfs(int u,int fa,vector<bool>&hasApple){ dep[u]=dep[fa]+1; if(hasApple[u-1]){ num[u]=1; ans+=dep[u]*2; } else num[u]=0; for(auto v:son[u]){ if(v==fa) continue; dfs(v,u,hasApple); ans-=max(0,(num[v]-1))*2; num[u]+=num[v]; } } int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) { ans=0; dep[0]=-1; for(auto it:edges){ son[it[0]+1].push_back(it[1]+1); son[it[1]+1].push_back(it[0]+1); } dfs(1,0,hasApple); return ans; } };
1444 切披薩的方案數
求方案數,顯然用dp求
因爲每次都會留下右下部分,所以每次切的時候,相當於選擇了一個矩形的左上角
dp[i][j][l] dp數組意義:以(i,j)爲左上角,已經分成l次的方案數 所以對於每個狀態(i,j,l),我們去枚舉下一切的左上角作爲下一個狀態去轉移 1)當水平切的時候 dp[ii][j][l+1]=(dp[ii][j][l+1]+dp[i][j][l])%mod; ii是水平切的行座標 2)當豎直切的時候 dp[i][jj][l+1]=(dp[i][jj][l+1]+dp[i][j][l])%mod; jj是豎直切的列座標 有了轉移方程,我們現在要考慮題目裏的另一個限制條件,每一塊分區都必須至少有一個蘋果,也就是說只有滿足條件纔會發生狀態轉移 一個矩形內有無蘋果,可以把有蘋果的點記爲1,其他記爲0 然後前綴和差分來求出一個矩形是否含有1,關於前綴和差分,有不清楚的可以搜博客學習一下
const long long mod=1e9+7; class Solution { public: int ways(vector<string>& pizza, int k) { int row=pizza.size(); int col=pizza[0].size(); long long sum[55][55],dp[55][55][15]; memset(sum,0,sizeof(sum)); memset(dp,0,sizeof(dp)); for(int i=1;i<=row;i++){ for(int j=1;j<=col;j++){ sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(pizza[i-1][j-1]=='A'); } } dp[1][1][1]=1; long long ans=0; for(int i=1;i<=row;i++){ for(int j=1;j<=col;j++){ for(int l=1;l<=k;l++){ if(!dp[i][j][l]) continue; //水平切 for(int ii=i+1;ii<=row;ii++){ int flag=1; if(sum[ii-1][col]-sum[ii-1][j-1]-sum[i-1][col]+sum[i-1][j-1]) flag&=1; else flag=0; if(sum[row][col]-sum[row][j-1]-sum[ii-1][col]+sum[ii-1][j-1]) flag&=1; else flag=0; if(flag){ dp[ii][j][l+1]=(dp[ii][j][l+1]+dp[i][j][l])%mod; } } //豎直切 for(int jj=j+1;jj<=col;jj++){ int flag=1; if(sum[row][jj-1]-sum[row][j-1]-sum[i-1][jj-1]+sum[i-1][j-1]) flag&=1; else flag=0; if(sum[row][col]-sum[row][jj-1]-sum[i-1][col]+sum[i-1][jj-1]) flag&=1; else flag=0; if(flag){ dp[i][jj][l+1]=(dp[i][jj][l+1]+dp[i][j][l])%mod; } } } ans=(ans+dp[i][j][k])%mod; } } return ans; } };