第一题-2578. 最小和分割
【贪心】
- 把num各位数字取出放到vec里,升序排列
- 对于新组成的两个数(设为a、b),每次从vec里拿取两个最小的数分别拼接到它们后面形成新的数
- 借助
x = x*10 + vec[i]
- 因为最后形成的两个数位数要么一样,要么位数之差为1
- 取决于原数num的位数是奇还是偶
- 若为偶,a、b位数一样,拿取的两个最小数放置顺序无所谓
- 因为当前最小的两个数一定在同一位上,交换放和也不会变
- 若为奇,a、b位数差1,应该把拿取的两个最小数中更小的那个放在a、b中更小的那个数后面
- 这样保证每次都把当前未访问过最小数字给到位数多的那个数,从而使得生成的两数和最小
- 若为偶,a、b位数一样,拿取的两个最小数放置顺序无所谓
- 取决于原数num的位数是奇还是偶
- 借助
- 对于新组成的两个数(设为a、b),每次从vec里拿取两个最小的数分别拼接到它们后面形成新的数
int splitNum(int num) {
vector<int> vec;
while(num){
vec.push_back(num%10);
num/=10;
}
int len = vec.size();
sort(vec.begin(), vec.end());
int num1 = 0, num2 = 0;
int i = 0;
for(; i < len; i+=2){
num1 = num1*10 + vec[i];
if(i+1 < len) num2 = num2*10 + vec[i+1];
}
return num1+num2;
}
};
第二题-2579. 统计染色格子数
【数学, 找规律】
- n=k时,
- 把图形分为上下两部分,其中上半部分包括中间的那行
- 中间那行:2k-1
- 上半部分:形成{1, 3, …, 2k-1}的等差数列
- 首项为1,公差为2,末项为(2k-1),项数为k
- 总数(即和)= ( 1 + ( 2 k − 1 ) ) ∗ k 2 = k 2 \frac{(1+ (2k-1))*k}{2}=k^2 2(1+(2k−1))∗k=k2
- 下半部分:形成{1,3,…}的等差数列
- 比起上半部分,就少了(2k-1)这一项
- 所以和减掉(2k-1),即 k 2 − ( 2 k − 1 ) k^2-(2k-1) k2−(2k−1)
- 总和 = 2 k 2 − 2 k + 1 =2k^2-2k+1 =2k2−2k+1
long long coloredCells(int n) {
return 2ll * n * n - 2ll * n + 1;
}
第三题-2580. 统计将重叠区间合并成组的方案数
【贪心】
- 合并区间
- 贪心:左端点升序排序
- 从头遍历,记录当前比较过的最大右端点值
- 对第i个区间:
- 若区间左端点>right,说明不相交
- ++cnt
- 更新right = max(right, interval.right)
- 若区间左端点<=right,说明相交
- 更新right = max(right, interval.right)
- 若区间左端点>right,说明不相交
- 设合并后的区间有cnt个
- 贪心:左端点升序排序
- 因为要分成两组,所以对于每个合并后的区间,有2种选择
- 去第1组还是去第2组
- 所以答案为 2 c n t 2^{cnt} 2cnt
const int mod = 1e9+7;
int countWays(vector<vector<int>>& ranges) {
sort(ranges.begin(), ranges.end(), cmp);
int cnt = 0, right = -1;
for(vector<int> interval: ranges){
if(interval[0] > right) ++cnt;
right = max(right, interval[1]);
}
int ans = 1;
for(int i = 1; i <= cnt; i++){
ans = (2ll *ans)%mod;
}
return ans;
}
static bool cmp(vector<int>&a, vector<int>&b){
return a[0] == b[0]? a[1] > b[1] : a[0] < b[0];
}
第四题-2581. 统计可能的树根数目
【树形DP-换根DP】
节点编号0~(n-1)
- 换根DP,需要两次DFS
- 第一次:
- 求出以0为根的树的guess正确个数
- BFS:设当前节点为u
- $cnt[u] = \sum_{v为u的子节点}{cnt[v] + ifExitGuess(u->v)} $
- 其中
ifExitGuess(u->v)
- =1:存在一个u->v的guess
- =0:不存在一个u->v的guess
- 其中
- $cnt[u] = \sum_{v为u的子节点}{cnt[v] + ifExitGuess(u->v)} $
- 第二次:
- 换根,求出以i为根的数的guess正确个数
- i为0~(n-1)
- 求状态转移:
- 设u为根,v为其子节点
- 则
dp[v] = dp[u] + ifExitGuess(u->v) - ifExitGuess(v->u)
- 则
- 从第一次DFS的根节点0开始,递归地往下求
- 答案就是
count{dp[i]>=k} (0<=i<=n-1)
- 设u为根,v为其子节点
- 换根,求出以i为根的数的guess正确个数
- tips:
- 标记gusses(u->v)是否存在
- 除了可以用
map<pair<int, int>, bool>
外,- 注意
unordered_map
的key不能用pair
- 注意
- 还可以
set<long> st;
//或unordered_map<long, bool> mp;
- u和v都是int型,占4字节
- long占8字节,前4字节标记u,后四字节标记v
- 状态压缩
- 用法:
st.insert(((long)u << 32 | v));
- 若求gusses(u->v)出现的次数:
unordered_map<long,int> mp;
- 除了可以用
- 标记gusses(u->v)是否存在
- 第一次:
使用set+状态压缩来标记:
set<long> st;
int rootCount(vector<vector<int>>& edges, vector<vector<int>>& guesses, int k) {
int n = edges.size()+1;
vector<int> dp(n, 0);
vector<vector<int>> graph(n);
for(int i = 0; i <edges.size(); i++){
int u = edges[i][0], v = edges[i][1];
graph[u].push_back(v);
graph[v].push_back(u);
}
for(int i = 0; i < guesses.size(); i++){
int u = guesses[i][0], v = guesses[i][1];
st.insert((long)u << 32 | v);
}
dp[0] = dfs(0, graph, -1);
rootCnt(0, graph, dp, -1);
int cnt = 0;
for(int i = 0; i < n; i++){
if(dp[i] >= k) ++cnt;
}
return cnt;
}
int dfs(int u, vector<vector<int>>&graph, int fa){
int cnt = 0;
for(int i = 0; i < graph[u].size(); i++){
int v = graph[u][i];
if(v == fa) continue;
cnt += dfs(v, graph, u) + st.count((long)u << 32 | v);
}
return cnt;
}
void rootCnt(int u, vector<vector<int>>&graph, vector<int>& dp, int fa){
for(int i = 0; i < graph[u].size(); i++){
int v = graph[u][i];
if(v == fa) continue;
dp[v] = dp[u] + st.count((long)v << 32 | u) - st.count((long)u << 32 | v);
rootCnt(v, graph, dp, u);
}
}