Codeforces Round #661 (Div. 3) D、E1 題解
D. Binary String To Subsequences #貪心 #構造
題目連結
題意
給定一個\(01\)串\(s\),完全分割成若干子序列(注意,不要混淆子串與子序列的概念),其中的子序列不包含兩個相鄰的\(0\)或\(1\)\((eg:"0101", "1010")\)。對\(s\)按這樣的分割方式,最少能分出多少串子序列?同時,還要求輸出\(s\)串中每一字元所在的分割出的子序列編號
分析
參考了官方題解的思路,我們可以定義兩個陣列為\(now0\)和\(now1\),\(now0\)存下了當前以0為結尾的所有10子序列,\(now1\)反之。
假定我們遇到\(str[i]\)
如果存在,我們就接入(注意,接入後,該子序列的末尾是'0'了,因而需要在\(now0\)陣列留下標記);如果不存在,我們只能從\(now0\)中建立一個新編號,注意這個編號是基於所有子序列的數量的。
#include <string> #include <cstdio> #include <iostream> #include <vector> #include <string> using namespace std; typedef long long ll; int main(){ int q; scanf("%d", &q); while(q--){ vector<int> ans; vector<int> now1, now0; int n; scanf("%d", &n); string str; cin >> str; //利用vector模擬過程,便於檢視當前集合的元素個數,及增加/修改元素 for (int i = 0; i < n; i++){ int newkey = now0.size() + now1.size();//newkey用於統計最新子序列編號(從0開始) if(str[i] == '0'){ if(now1.empty()){ //暫無最新的01串,只能構建以0為開頭的新子序列 now0.push_back(newkey); } //新子串+1 else{ //當前存在可接入的01串,為該串的尾部放置合適位置 newkey = now1.back(); //注意獲取最新01串的編號 now0.push_back(newkey); //第newkey串結尾改為0 now1.pop_back(); //第newkey串結尾不再是1 } } else { if(now0.empty()){ //暫無最新的10串 now1.push_back(newkey); } else{ newkey = now0.back(); now1.push_back(newkey); now0.pop_back(); } } ans.push_back(newkey); } printf("%d\n", (int)(now1.size() + now0.size())); for (int i = 0; i < n; i++){ printf("%d%c", ans[i] + 1, (i == n - 1) ? '\n' : ' '); } } }
其實\(for\)迴圈內部不需要分出兩個大\(if\)判斷,可以定義\(now[2][...]\)然後通過\(now[!newkey]\)進行一系列操作即可
E1. Weights Division #貪心 #大頂堆
題目連結
題意
給定每條邊帶一定權值的有根樹。你可以將任意一條邊的權值減少一半,即\(w_i:=wi/2\)(向下取整),每一次對某一邊權值減少一半記為一次操作。定義樹的總重量:根節點到所有葉子的路徑權值之和。現給定\(S\),需要你用最少的操作次數使得樹的總重量不超過\(S\)。
分析
根節點到達某一葉子的路徑,與 根節點到達另一葉子的路徑,很有可能發生部分重合。而題幹中不遺漏路徑中所有經過邊的權值,所以我們需要提前將任何邊的經過次數\(num\)
我們很容易往貪心的方向考慮。貪心什麼?我最初貪心,是將當前所有邊中,出現次數\(\times\)邊權得到的加權最大的邊挑出來並進行修改。然而,這樣不準確,比如{\(w_1=5, num_1=4\)}與{\(w_2=2, num_2=10\)},他們總加權相等,但由於邊權值縮小是向下取整,5縮小一半對總權重的影響比2縮小一半更大。從這裡我們可以發現,貪心的目標應該是選擇縮小對樹重量的影響最大的邊即縮小前與縮小後之差最大。
#include <string>
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
int H[MAXN], tot = 0;
ll s = 0, n, cursum = 0;
typedef struct Edge {
int to, nextNbr, num; ll w; //num記錄該邊的經過次數
bool operator < (const struct Edge& x) const {
return num * (w - w / 2) < x.num * (x.w - x.w / 2);
} //對總權重影響最大的,優先取出
}Edge;
Edge E[MAXN * 2];
priority_queue<Edge> myque;
void addEdge(int u, int v, ll w) {
tot++;
E[tot].to = v; E[tot].w = w; E[tot].nextNbr = H[u]; E[tot].num = 1;
H[u] = tot;
}
void Init() {
for (int i = 1; i <= n; i++) H[i] = -1;
tot = 0; cursum = 0;
while (!myque.empty()) myque.pop();
}
int dfs(int now, int pre) {
int cnt = 0; //cnt記錄從now(除了葉節點)出發,它的出邊的經過次數
bool f = false;
for (int i = H[now]; i >= 0; i = E[i].nextNbr) {
if (E[i].to != pre) { //保證不回退
E[i].num = dfs(E[i].to, now); //回溯返回該邊的經過次數
cnt += E[i].num;
myque.push(E[i]);
cursum += (ll)E[i].num * E[i].w; //累加當前樹的總權重
f = true;
}
}
return f ? cnt : 1; //如果是葉子節點即返回1,否則返回給上一條邊的經過次數
}
int main() {
int q; scanf("%d", &q);
while (q--) {
scanf("%lld%lld", &n, &s); Init();
for (int i = 1; i <= n-1; i++) {
int u, v; ll w;
scanf("%d%d%lld", &u, &v, &w);
addEdge(u, v, w); addEdge(v, u, w);
}
dfs(1, 1); //對每一條邊的出現次數及所有邊的總權重作統計
ll ans = 0;
while (cursum > s) {
Edge tmp = myque.top(); myque.pop();
cursum -= tmp.w * tmp.num;
tmp.w = (ll)(tmp.w / 2); //該邊權值
cursum += tmp.w * tmp.num;
myque.push(tmp);
ans++;
}
printf("%lld\n", ans);
}
return 0;
}