Codeforces Round #661 (Div. 3)
#661 (Div. 3) 3.2
题,给出一个01序列,问可以最少将其分成多少个子序列,使得每个子序列中都是01交替的(就是不存在00或者11的情况)
队列维护当前以0或者1结尾的子序列信息,如果当前点为1,考虑是否还有结尾为0的,有就加进去,没有的话就只能新建一个
void solve(int group_Id) {
int n, cnt = 0, ans = 0;
read(n);
string s;
cin >> s;
queue<int> q1, q0;
VI res(n + 1);
for (auto c : s) {
++ cnt;
if (c == '0') {
if (!q1.empty()) {
int t = q1.front();
q1.pop();
res[cnt] = res[t];
q0.push(cnt);
}else {
q0.push(cnt);
res[cnt] = ++ ans;
}
}else {
if (!q0.empty()) {
int t = q0.front();
q0.pop();
res[cnt] = res[t];
q1.push(cnt);
}else {
q1.push(cnt);
res[cnt] = ++ ans;
}
}
}
cout << ans << endl;
for (int i = 1;i <= n;i ++) {
cout << res[i] << " ";
}
cout << endl;
}
,一颗以1为根的树,该树的贡献为从根节点1到每个节点的距离之和,现在每次可以选择一条边,将这条边的权值除二向下取整,问最少操作几次可以使得贡献小于S
考虑每条边在计算总贡献时用到了cnt次,那么对当前边操作,显然是使得总贡献减少了这么多,根据贪心,我们每次希望这个数最大,这里用优先队列来实现
然后每次取出即可,然后再将操作完后的边加入优先队列中去
int n;
ll sum, now;
int h[N], e[M], ne[M], idx;
ll w[M];
struct cmp {
bool operator () (PLL a, PLL b) const {// 定义排序规则
return (a.first - a.first / 2) * a.second < (b.first - b.first / 2) * b.second;
}
};
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void solve(int group_Id) {
read(n, sum);
now = 0;
for (int i = 1;i <= n;i ++) h[i] = -1;
idx = 0;
VI d(n + 1, 0);
for (int i = 1, u, v, w;i < n;i ++) {
read(u, v, w);
add(u, v, w);
add(v, u, w);
d[u] ++, d[v] ++;// 用来判断叶子节点
}
VI cnt(n + 1);
priority_queue<PLL, vector<PLL>, cmp> q;
function<void(int, int)> dfs1 = [&](int u, int fa) {
if (d[u] == 1 && u != 1) {
cnt[u] = 1;
return ;
}
for (int i = h[u];~i;i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs1(v, u);
cnt[u] += cnt[v];
}
};
function<void(int, int)> dfs2 = [&](int u, int fa) {
for (int i = h[u];~i;i = ne[i]) {
int v = e[i];
if (v == fa) continue;
now += w[i] * cnt[v];// 计算总贡献
q.push({w[i], cnt[v]});// 对于该边,加入队列中去
dfs2(v, u);
}
};
dfs1(1, -2);
dfs2(1, -2);
// for (int i = 1;i <= n;i ++) {
// out(cnt[i]);
// }
int res = 0;
while (now > sum) {
now -= (q.top().first - q.top().first / 2) * q.top().second;
q.push({q.top().first / 2, q.top().second});// 操作完后的边
q.pop();
res ++;
}
cout << res << endl;
}
,是在的基础上做了加强,现在对于每条边多了一个权值,1或者2,代表如果操作该边,需要花费1或者2,现在问最小花费
显然是可以根据上题一样,采用两个优先队列去维护,但是需要判断的条件过于繁琐,所以这里换一种方法
对于花费为1的边放到集合里面,求出来代表对这个集合里面操作i次,会减少多少贡献,当然方法也是根据上题一样,用优先队列去贪
对于花费为2的同理,求出了
然后,now作为我们最初的总贡献,当我们的时候,显然是一个答案,为了找到最小的答案,我们i从0到cnt1,j从cnt2到0,每次合法状态取
int n;
ll sum, now;
int h[N], e[M], ne[M], idx;
ll w[M];
int ww[M];
struct cmp {
bool operator () (PLL a, PLL b) const {
return (a.first - a.first / 2) * a.second < (b.first - b.first / 2) * b.second;
}
};
void add(int a, int b, int c, int d) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], ww[idx] = d, h[a] = idx ++ ;
}
void solve(int group_Id) {
read(n, sum);
now = 0;
for (int i = 1;i <= n;i ++) h[i] = -1;
idx = 0;
VI d(n + 1, 0);
for (int i = 1, u, v, w, c;i < n;i ++) {
read(u, v, w, c);
add(u, v, w, c);
add(v, u, w, c);
d[u] ++, d[v] ++;
}
VI cnt(n + 1);
priority_queue<PLL, vector<PLL>, cmp> q1;
priority_queue<PLL, vector<PLL>, cmp> q2;
function<void(int, int)> dfs1 = [&](int u, int fa) {
if (d[u] == 1 && u != 1) {
cnt[u] = 1;
return ;
}
for (int i = h[u];~i;i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs1(v, u);
cnt[u] += cnt[v];
now += cnt[v] * w[i];
if (ww[i] == 1) q1.push({w[i], cnt[v]});
else q2.push({w[i], cnt[v]});
}
};
dfs1(1, -1);
VL dis1(n * 22), dis2(n * 22);
int cnt1 = 0, cnt2 = 0;
auto calc = [](VL &dis, priority_queue<PLL, vector<PLL>, cmp> &q, int &ans) {// 计算dis数组
for (int i = 1;!q.empty();i ++) {
auto t = q.top();
q.pop();
dis[i] = dis[i - 1] + (t.first - t.first / 2) * t.second;
if (dis[i] == dis[i - 1]) break;
q.push({t.first / 2, t.second});
ans = i;
}
};
calc(dis1, q1, cnt1);
calc(dis2, q2, cnt2);
ll D = now - sum;
if (D <= 0) {
cout << 0 << endl;
return ;
}
ll res = (ll)inf * inf;
int j = cnt2;
for (int i = 0;i <= cnt1;i ++) {// i 和 j 可以颠倒,都符合要求
while (j && dis2[j - 1] + dis1[i] >= D) j --;
if (dis1[i] + dis2[j] >= D) {
Min(res, (ll)i + j * 2);
}
}
cout << res << endl;
}
,区间dp问题,给出个线段在x轴上,分别代表这条线段的起点和终点,线段不重复,现在问在x轴上,取一个区间,使得这个区间内合法线段的个数最多
合法线段:线段之间可以存着包含关系,其中端点可以重复,如果没有包含关系,那么不可以相交,其中端点也不可以重复
一般的区间dp我们习惯是三重循环:枚举起点,枚举长度,枚举中间点,但是显然这里不适用,n3的复杂度会超时
所以考虑n2的复杂度可不可以,那么不可以枚举中间的,我们状态转移就只能:这样去转移,然后考虑中间的覆盖问题
由于线段不重复,我们考虑以l为起点的所有线段,用sta数组记录下来以l为起点的终点的坐标,那么转移:
最后别忘记每个线段的贡献,如果是存在线段的,就再让
void solve(int group_Id) {
int n;
read(n);
VP line(n + 1);
VI dic;
for (int i = 1, l, r;i <= n;i ++) {
read(l, r);
line[i] = {l, r};
dic.pb(l);
dic.pb(r);
}
sort(all(dic));
dic.erase(unique(all(dic)), dic.end());// 数据还是比较大的,离散化一下
int cnt = len(dic) + 1;
auto get = [&](int x) {
return lower_bound(all(dic), x) - dic.begin() + 1;
};
for (int i = 1;i <= n;i ++) {
line[i] = {get(line[i].first), get(line[i].second)};
}
VVI sta(cnt + 1);
vector<vector<bool>> st(cnt + 1, vector<bool> (cnt + 1, false));
for (int i = 1;i <= n;i ++) {
// out(line[i].first, line[i].second, cnt)
sta[line[i].first].pb(line[i].second);// 在起点坐标的位置存下所有终点
st[line[i].first][line[i].second] = true;// 表示st[l][r]存在线段
}
VVI f(cnt + 3, VI (cnt + 3));
for (int len = 1;len <= cnt;len ++) {
for (int l = 1;l + len - 1 <= cnt;l ++) {
int r = l + len - 1;
f[l][r] = max(f[l + 1][r], f[l][r - 1]);
for (auto pos : sta[l]) {// 枚举所有终点
if (pos > r) continue;
f[l][r] = max(f[l][pos] + f[pos + 1][r], f[l][r]);
}
if (st[l][r]) f[l][r] ++;// 最后别忘了+1
}
}
cout << f[1][cnt] << endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)