2025.2.20 鲜花
ABC221G 和 P5163 题解
命に嫌われている。
「死にたいなんて言うなよ。」
「諦めないで生きろよ。」
そんな歌が正しいなんて馬鹿げてるよな。
実際自分は死んでもよくて
周りが死んだら悲しくて
「それが嫌だから」っていうエゴなんです。
他人が生きてもどうでもよくて
誰かを嫌うこともファッションで
それでも「平和に生きよう」なんて
素敵なことでしょう。
画面の先では誰かが死んで
それを嘆いて誰かが歌って
それに感化された少年がナイフを持って走った。
僕らは命に嫌われている。
価値観もエゴも押し付けて
いつも誰かを殺したい歌を
簡単に電波で流した。
僕らは命に嫌われている。
軽々しく死にたいだとか
軽々しく命を見てる 僕らは命に嫌われている。
お金がないので今日も 一日中惰眠を謳歌する。
生きる意味なんて見出せず、
無駄を自覚して息をする。
「寂しい」なんて言葉でこの傷が表せていいものか
そんな意地ばかり抱え今日も一人ベッドに眠る
少年だった僕たちはいつか青年に変わってく。
年老いていつか枯れ葉のように
誰にも知られず朽ちていく。
不死身の身体を手に入れて、
一生死なずに生きていく。
そんなSFを妄想してる
自分が死んでもどうでもよくて
それでも周りに生きて欲しくて
矛盾を抱えて生きてくなんて怒られてしまう。
「正しいものは正しくいなさい。」
「死にたくないなら生きていなさい。」
悲しくなるならそれでもいいなら
ずっと一人で笑えよ。
僕らは命に嫌われている。
幸福の意味すらわからず
生まれた環境ばかり憎んで
簡単に過去ばかり呪う。
僕らは命に嫌われている。
さよならばかりが好きすぎて
本当の別れなど知らない 僕らは命に嫌われている。
幸福も別れも愛情も友情も
滑稽な夢の戯れで全部カネで買える代物。
明日死んでしまうかもしれない。
すべて無駄になるかもしれない。
朝も 夜も 春も 秋も
変わらず誰かがどこかで死ぬ。
夢も明日も何もいらない。
君が生きていたならそれでいい。
そうだ。本当はそういうことが歌いたい。
命に嫌われている。
結局いつかは死んでいく。
君だって僕だっていつかは枯れ葉のように朽ちてく。
それでも僕らは必死に生きて
命を必死に抱えて生きて
殺して、足掻いて、笑って、抱えて
生きて、生きて、生きて、生きて、生きろ。
ABC221G
省流:可以直接看最下面 wang54321 的写法,应该是最简洁的。
2025.7.25 Upd:别省了,好像假了,具体可以看下面。
首先假装大家都会 \(n^2D\) 的做法,简单说就是考虑分别计算出 \(X + Y\) 和 \(X - Y\) 的方案(也可以说是转坐标轴),然后就可以得 \(X\) 和 \(Y\) 并推出方案。
经过一些转化问题成功变成了如下经典问题:
有 \(n\) 个物品,每个的重量 \(w \le D\) 求是否可以选出若干个使其重量是 \(T\)。
使用 bitset
不难给出 \(\mathcal{O}(\frac{nT}w)\) 的做法,但在这个题 \(T\sim nD\) 并不足够优秀。
考虑能否 \(nD\) 解决问题。
容易想到经典结论,先从前往后尽可能多的取,设取到位置 \(p\) 和为 \(S\),显然有 \(S\in(T - D, T]\),然后考虑背包微调,有结论其在微调的任意时刻一定可以满足 \(S\in(T - D, T + D]\),证明不太难。
设 \(f_{l, r, w} = 0/1\) 表示在 \(l \le p \le r\) 中微调能否达到 \(w\) 的权值,显然 \(w\) 的范围只有 \(2D\),每次分讨向左是否扔掉和向右拿上即可转移,设序列为 \(a\),给个方程。
其依然是 \(n^2D\) 的,考虑优化,发现当 \(r, w\) 固定时 \(f_{l, r, w}\) 为 \(1\) 是 \(l\) 的一段前缀,于是转而设 \(g_{r, w} = l\) 表示最大的 \(l\) 满足 \(f_{l, r, w} = 1\),若没有就是 \(0\)。
转移依然分讨左右是否改变,这里由于是最大所以不用考虑向左但不扔的转移,方程:
由于第三个转移,我们依然要枚举 \(l\),复杂度没变。
但是我们惊奇的发现,对于 \(l < g_{r - 1, w}\) 的 \(l\),其可以通过 \(l \to g_{r - 1, w} \to g_{r, w}\) 转移,于是我们的 \(l\) 只用枚举 \([g_{r - 1, w}, g_{r, w})\) 的了。
显然将 \(w\) 相等的拼起来其最多是 \(\mathcal{O}(n)\) 的,于是我们成功做到了 \(nD\)。
主播主播,上面的一堆推导还是太需要操作了,有没有简单无脑还快的做法呢?
还真有,考虑 wang54321 的简单做法。
依然是先尽可能多选,根据结论这时我们只会在值域 \((T - D, T + D]\) 上跳,并且我们已经跳到一个点上了。
于是乎我们直接暴力枚举序列中每个数,以此取反其选取情况,就会得到 \(n\) 个新状态,每次扩展到没有被跳过的点上再次暴力即可,容易发现一共只有 \(\mathcal{O}(D)\) 个点,每个点扩展 \(n\) 次,复杂度依然是 \(nD\) 的。原理其实和上面的很类似。
2025.7.25 Upd 今天又看了这个做法,感觉没什么道理,一拍果然挂了……
给出过程,大家可以看看是不是我写的问题
点击查看
brute:
/*
ulimit -s 128000 && clear && g++ % -o %< -O2 -std=c++14 -DLOCAL -Wall -Wextra && time ./%< && size %<
ulimit -s 128000 && clear && g++ % -o %< -O2 -std=c++14 -DLOCAL -Wall -Wextra -fsanitize=address,undefined -g && time ./%< && size %<
echo && cat out.out && echo
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using llf = long double;
using ull = unsigned long long;
#define endl '\n'
#ifdef LOCAL
FILE *InFile = freopen("in.in", "r", stdin), *OutFile = freopen("out.out", "w", stdout);
#endif
const int M = 103, N = 103, V = N * M;
bitset<V> f; int n;
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n; int s = 0;
f[0] = 1; for(int i = 1, v; i <= n; ++i) cin >> v, f = f | f << v, s += v;
for(int i = 1; i <= s; ++i) if(f[i]) cout << i << endl;
}
wang54321's trick
/*
ulimit -s 128000 && clear && g++ % -o %< -O2 -std=c++14 -DLOCAL -Wall -Wextra && time ./%< && size %<
ulimit -s 128000 && clear && g++ % -o %< -O2 -std=c++14 -DLOCAL -Wall -Wextra -fsanitize=address,undefined -g && time ./%< && size %<
echo && cat ans.ans && echo
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using llf = long double;
using ull = unsigned long long;
#define endl '\n'
#ifdef LOCAL
FILE *InFile = freopen("in.in", "r", stdin), *OutFile = freopen("ans.ans", "w", stdout);
#endif
const int M = 103, N = 103, V = N * M;
int n, _v[N], s = 0; bool vis[V];
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> _v[i], s += _v[i];
queue<pair<array<bool, N>, int>> que;
que.emplace(array<bool, N>{0}, 0);
while(!que.empty()){
auto [f, v] = que.front(); que.pop();
if(vis[v]) continue; else vis[v] = 1;
for(int i = 1; i <= n; ++i) f[i] ^= 1, que.emplace(f, v + (f[i] ? _v[i] : -_v[i])),f[i] ^= 1;
}
for(int i = 1; i <= s; ++i) if(vis[i]) cout << i << endl;
}
可以看出是以 01
串输出求的 \(n\) 个数能组成的大小。
hack:
10
12 38 11 28 49 4 9 41 38 42
wang54321's trick 输出 \(109\) 是 \(0\),实际是 \(1\)。
这里给出第一种做法的代码。
Code
/* Local File
in_out/in.in
in_out/out.out
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using llf = long double;
using ull = unsigned long long;
#ifndef LOCAL
#undef assert
#define assert 0 &&
#define cerr 0 && cerr
#endif
const int N = 2003, D = 1803;
int n, cx, cy, cd[N];
bool cv1[N], cv2[N];
int _f[D * 2], *const f = _f + D, _g[D * 2], *const g = _g + D, chs[N][D * 2];
/*
for : r
g_w <- f_w
g_{w + v_r} <- f_w | w <= 0
g_{w - v_l} <- l | f_w < l < g_w && w >= 0
*/
bool Dp(int tw, bool *ans){
int p = 1, s = 0;
while(p <= n && s + cd[p] <= tw)
s += cd[p++];
memset(_f, 0, sizeof _f), memset(_g, 0, sizeof _g), memset(chs, 0, sizeof chs);
g[s - tw] = f[s - tw] = p;
for(int r = p; r <= n; ++r){
auto nc = chs[r] + D;
auto Upd = [&](int p, int v, int f){
if(g[p] < v)
g[p] = v, nc[p] = f;
};
for(int w = -D + 3; w <= 0; ++w)
Upd(w + cd[r], f[w], -1);
for(int w = D - 3; ~w; --w)
for(int l = f[w]; l < g[w]; ++l)
Upd(w - cd[l], l, l);
memcpy(_f, _g, sizeof _f);
}
if(!g[0]) return 0;
for(int i = 1; i < p; ++i)
ans[i] = 1;
int r = n, v = 0;
while(r >= p){
int f = chs[r][v + D];
if(f == 0) --r;
else if(f == -1)
ans[r] = 1, v -= cd[r], --r;
else if(f > 0)
ans[f] = 0, v += cd[f];
}
return 1;
}
int main(){
cin >> n >> cx >> cy; int s = 0;
for(int i = 1; i <= n; ++i)
cin >> cd[i], s -= cd[i];
int b1 = cx + cy - s, b2 = cx - cy - s;
if(b1 < 0 || b2 < 0 || b1 % 2 || b2 % 2 || !Dp(b1 / 2, cv1) || !Dp(b2 / 2, cv2))
cout << "No" << endl;
else{
cout << "Yes" << endl;
for(int i = 1; i <= n; ++i)
if(cv1[i] == cv2[i])
cout << (cv1[i] ? 'R' : 'L');
else
cout << (cv1[i] ? 'U' : 'D');
}
}
P5163
如果是无向边是显的。
考虑有向边和无向边的区别是什么,其实没什么区别,只是我们需要知道每个边归到连通分量的时间。
考虑对于一条边如何确定,可以二分,每次判断加入 mid 之前的边自己是否归于一个连通分量。对于所有边,容易想到整体二分。
考虑 check,如果是暴力 tarjan 复杂度并不正确,容易想到的是先处理左边,在将左边处理剩下的残图加边接着跑,很可惜复杂的依然是错的,考虑若没有强连通分量,则每次最坏依然是 \(\mathcal{O}(n)\) 的。
这里有一个比较牛的做法,考虑每次先缩点,在将缩了的点放到左边,不属于连通分量的边放到右边。考虑这样我们和上面的区别,我们每次把边分成了两边,避免了不能缩的边在左边继续计算,于是只要我们 tarjan 只做非孤立点,复杂度就是对的。
每层右边需要重标号,可以用并查集,也可以每次暴力整。
然后就是板子大战了。
Code
/* Local File
in_out/in.in
in_out/out.out
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using llf = long double;
using ull = unsigned long long;
#ifndef LOCAL
#undef assert
#define assert 0 &&
#define cerr 0 && cerr
#endif
const int N = 2e6 + 3;
int n, m, q, cs[N];
struct Gph{
vector<int> to[N];
void Add(int u, int v){
to[u].emplace_back(v);
}
void ADD(int u, int v){
Add(u, v), Add(v, u);
}
void Clr(int u){
to[u].clear();
}
#define For_to(i, u, v, g) for(int v : g.to[u])
}g;
class TAR{
private:
int dfn[N], low[N], stk[N], *top = stk, tdn, tco = 1e5, col[N];
bool isk[N];
void Dfs(int u){
dfn[u] = low[u] = ++tdn;
isk[*++top = u] = 1;
For_to(i, u, v, g){
if(!dfn[v]) Dfs(v), low[u] = min(low[u], low[v]);
else if(isk[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]){
++tco; int v;
do{
v = *top--;
col[v] = tco, isk[v] = 0;
}while(v != u);
}
}
public:
void In(int u){
if(!dfn[u]) Dfs(u);
assert(top == stk);
}
int operator()(int u){
return col[u];
}
void Clr(int u){
dfn[u] = low[u] = col[u] = 0;
}
} Tar;
struct E{
int u, v, t, tc, uu, vv;
} ce[N];
bool Chk(const E &e){
return Tar(e.u) == Tar(e.v);
}
void Cdq(int l, int r, int ll, int rr){
if(l == r){
for(int i = ll; i <= rr; ++i)
ce[i].tc = l;
return ;
}
int mid = l + r >> 1, mmd;
vector<int> nd;
for(mmd = ll; mmd <= rr; ++mmd){
auto &[u, v, t, tc, uu, vv] = ce[mmd];
if(t > mid) break;
else{
g.Add(u, v);
nd.emplace_back(u), nd.emplace_back(v);
}
}
--mmd;
for(auto k : nd) Tar.In(k);
vector<E> tp;
int p = ll - 1;
for(int i = ll; i <= mmd; ++i){
if(!Chk(ce[i]))
tp.emplace_back(ce[i]);
else
ce[++p] = ce[i];
}
mmd = p;
for(auto &k : tp) ce[++p] = k;
auto Id = [](int &u){
if(Tar(u)) u = Tar(u);
};
for(int i = mmd + 1; i <= rr; ++i)
Id(ce[i].u), Id(ce[i].v);
for(auto k : nd)
Tar.Clr(k), g.Clr(k);
Cdq(l, mid, ll, mmd), Cdq(mid + 1, r, mmd + 1, rr);
}
namespace SEG{
const int D = 1e7 + 3, V = 1e9;
int sz[D], ls[D], rs[D], tot; llt sm[D];
#define lson ls[t]
#define rson rs[t]
#define mid (l + r >> 1)
void Upd(int t){
sz[t] = sz[lson] + sz[rson];
sm[t] = sm[lson] + sm[rson];
}
llt qry(int l, int r, int k, int t){
if(!t)
return 0;
if(l == r)
return 1ll * l * k;
else{
if(k <= sz[rson]) return qry(mid + 1, r, k, rson);
else return qry(l, mid, k - sz[rson], lson) + sm[rson];
}
}
void add(int l, int r, int p, int v, int &t){
if(!t) t = ++tot;
if(l == r)
sz[t] += v, sm[t] += 1ll * l * v;
else{
if(p <= mid) add(l, mid, p, v, lson);
else add(mid + 1, r, p, v, rson);
Upd(t);
}
}
void mrg(int l, int r, int _, int &t){
if(!_ || !t) t += _;
else if(l == r){
sz[t] += sz[_], sm[t] += sm[_];
}else{
mrg(l, mid, ls[_], lson);
mrg(mid + 1, r, rs[_], rson);
Upd(t);
}
}
#undef lson
#undef rson
#undef mid
class Seg{
private:
int rt = 0;
public:
void Add(int a, int v){
add(0, V, a, v, rt);
}
llt Qry(int k){
return qry(0, V, k, rt);
}
void Mrg(const Seg &_){
mrg(0, V, _.rt, rt);
}
};
} using SEG::Seg;
Seg seg[N];
class Dsu{
private:
int fa[N];
public:
int Fa(int u){
return fa[u] ? fa[u] = Fa(fa[u]) : u;
}
void Uni(int fu, int fv){ // fu -> fv
fa[fu] = fv;
}
} uf;
stack<tuple<int, int, int, int>> cq;
int main(){
cin >> n >> m >> q;
{
map<pair<int, int>, int> eid;
for(int i = 1; i <= n; ++i)
cin >> cs[i];
for(int i = 1; i <= m; ++i){
int u, v; cin >> u >> v;
ce[i] = {u, v, 0, 0, u, v}, eid[{u, v}] = i;
}
for(int i = 1; i <= q; ++i){
int op, a, b; cin >> op >> a >> b;
int t = q - i + 1;
if(op == 1)
ce[eid[{a, b}]].t = t;
else{
cq.emplace(t, op - 1, a, b);
if(op == 2)
cs[a] += b;
}
}
for(int i = 1; i <= n; ++i) seg[i].Add(cs[i], 1);
}
sort(ce + 1, ce + m + 1, [](const E &a, const E &b){return a.t < b.t;});
Cdq(0, q + 1, 1, m);
for(int i = 1; i <= m; ++i)
ce[i].u = ce[i].uu, ce[i].v = ce[i].vv;
auto ne = ce + 1;
stack<llt> aas;
for(int t = 0; t <= q; ++t){
while(ne->tc == t){
int fu = uf.Fa(ne->u), fv = uf.Fa(ne->v);
if(fu != fv) uf.Uni(fu, fv), seg[fv].Mrg(seg[fu]);
++ne;
}
while(!cq.empty() && get<0>(cq.top()) == t){
auto [t, op, a, b] = cq.top(); cq.pop();
int f = uf.Fa(a);
if(op == 1)
seg[f].Add(cs[a], -1), seg[f].Add(cs[a] -= b, 1);
else
aas.emplace(seg[f].Qry(b));
}
}
while(!aas.empty())
cout << aas.top() << endl, aas.pop();
}
P
本文来自博客园,作者:xrlong,转载请注明原文链接:https://www.cnblogs.com/xrlong/p/18725242
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。