R12
A.QOJ1963
想的时候方向错了,一直在想两个杯子互相倒最终能倒出怎样的结果。但发现写出这个之后对推广到三个的情况并没有什么用。
不妨设三杯水是 \(a < b < c\),考虑 \(a = 1\) 时怎么做。由于 \(a\) 可以不断倍增,我们发现只需要让 \(b\) 按二进制分解之后为 1 的那些位置减掉即可,也就是如果 \(b\) 这一位为 1 就用 \(b\) 减,否则用 \(c\),注意到 \(b < c\),因此不可能被减为负的。
进一步地,当 \(a \ne 1\) 时,类似地考虑 \(t = \lfloor \frac{b}{a} \rfloor\) 的二进制分解,类似上面的操作,可以用 \(O(\log b - \log a)\) 的操作数把 \((a, b) \to (2^ka, b \bmod a)\)。而我们希望每次的最小值减半,此时只能得到 \(b \bmod a \le \frac{b}{2}\),因此不对。
考虑调整一下,当 \(b \bmod a \le \frac{a}{2}\) 时,按上面的做。
否则,我们希望让最后有一个数能是 \(a - b \bmod a\)。非常神秘的是我们让 \(t \gets t + 1\),还是按二进制分解,令 \(k = \lfloor \log(t) \rfloor\),到最后一步时是 \((2^ka, b - (t - 2^k)a)\),然后我们让前面到给后面,\((ta - b, 2(b - (t - 2^k)a))\)。我们考察前面那个数,发现正好是 \(a - (b - \lfloor \frac{b}{a} \rfloor a) = a - a \bmod b\)。同时也可以不难说明此时 \(b\) 不会被减完,考察一下 \(2^k \ge 1\) 即可。
这样,我们每次都可以让最小值减半,单次单价是 \(O(\log V)\),总共就是 \(O(\log^2 V)\)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
vector<pii> ans;
int a[4];
void pour(int x, int y){
ans.emplace_back(x, y);
a[x] -= a[y], a[y] <<= 1;
}
void work(int x, int y, int z){ // a[x] <= a[y]
if(a[x] == 0) return;
int k = a[y] / a[x], D;
if(2 * (a[y] % a[x]) <= a[x]){
D = __lg(k);
for(int p = 0; p <= D; ++p){
if(k & (1 << p)) pour(y, x);
else pour(z, x);
}
}
else{
++k;
D = __lg(k);
for(int p = 0; p < D; ++p){
if(k & (1 << p)) pour(y, x);
else pour(z, x);
}
pour(x, y);
}
// for(int i = 1; i <= 3; ++i) cout << a[i] << ' ';
// cout << '\n';
if(a[x] < a[z]) work(y, x, z);
else work(y, z, x);
}
void solve(){
ans.clear();
for(int i = 1; i <= 3; ++i) cin >> a[i];
work(1, 2, 3);
cout << ans.size() << '\n';
for(auto [x, y] : ans) cout << x << ' ' << y << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T; cin >> T; while(T--)
solve();
return 0;
}
B.QOJ6874
先稍微转换一下题意,整个图是强连通的 \(\iff\) 1 能到达所有点并且所有点能到达 1。
那么也就是说我们考虑原图和反图,只需要从 1 开始 bfs 一遍就可以判断。这里有一个用 bitset 优化的 trick,可以把 \(O(n + m)\) 做到 \(O(\frac{n^2}{\omega})\)。说来也简单,就是把 \(vis\) 建成一个 bitset 然后和邻接矩阵中的出边 & 一下什么的,再用 find_first 什么之类的,每次找到的点都只是没访问过的了,就把 \(m\) 去掉了。本体由于 \(n \le 50\),把他们压进 ull 里可以做到严格 \(O(n)\)。
接着考虑,现在已经转化为了一个从单点开始的连通性问题。我们考虑建一颗以 1 为根 bfs 生成树(实际上这里是不是 bfs 无所谓),要求原图和反图的生成树上都是 \(n\) 个点。并且我们发现,如果本次删边删的不是这两颗生成树上的一条,那么下一次肯定还是强连通的。因此我们每次枚举的数量就从 \(O(m) \to O(n)\),这样的话总的复杂度就变成了 \(O(n^4)\)。
计数时有一些细节,首先是不能把组合数当成排列数来记,因为可能删的边可能根本没出现在第一颗生成树上,这样会少算。其次就是既然我们拿组合数来算了,就要严格选定删边的顺序了,第一次删了,第二次就不能再删,对应的边数也要减去什么的。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 55;
ll ban[N], e[N], g[N], n, m, ans, c[2505][4];
int bfs(ll e[], vector<pii> &tr){
ll vis = ((1ll << (n + 1)) - 1) ^ 1;
int cnt = 0;
queue<int> q; q.push(1); vis ^= 2;
while(!q.empty()){
int u = q.front(); q.pop();
++cnt;
ll nxt = vis & e[u];
while(nxt){
int v = __builtin_ctzll(nxt);
nxt ^= (1ll << v);
vis ^= (1ll << v);
q.push(v);
tr.emplace_back(u, v);
}
}
return cnt;
}
void dfs(int x){
vector<pii> s, t;
if(bfs(e, s) < n || bfs(g, t) < n){
// cout << x << '\n';
// for(int i = 1; i <= n; ++i) cout << e[i] << ' ';
// cout << '\n';
ans += c[m][3 - x];
return;
}
if(x == 3) return;
vector<pii> del;
vector<ll> ch(n + 1, 0);
for(auto [u, v] : s){
if(ban[u] & (1ll << v)) continue;
ch[u] |= (1ll << v);
del.emplace_back(u, v);
}
for(auto [v, u] : t){
if((ban[u] & (1ll << v)) || (ch[u] & (1ll << v))) continue;
del.emplace_back(u, v);
}
for(auto [u, v] : del){
e[u] ^= (1ll << v); g[v] ^= (1ll << u);
m--; ban[u] |= (1ll << v);
dfs(x + 1);
e[u] ^= (1ll << v); g[v] ^= (1ll << u);
}
for(auto [u, v] : del) ban[u] ^= (1ll << v);
m += del.size();
}
void solve(){
cin >> n >> m;
memset(e, 0, sizeof(e)), memset(g, 0, sizeof(g));
ans = 0;
for(int i = 1; i <= m; ++i){
int u, v; cin >> u >> v;
e[u] |= (1ll << v);
g[v] |= (1ll << u);
}
dfs(0);
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
c[0][0] = 1;
for(int i = 1; i <= 2500; ++i){
c[i][0] = 1;
for(int j = 1; j <= 3; ++j) c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
}
int T; cin >> T;
while(T--) solve();
return 0;
}
C.If I Can Stop One Heart From Breaking
线段树分治的升级版。
我们考虑两个线段有交在线段树上如何表达,这相当于一定存在一队节点 \(p, q\),使得 \(p\) 在第一条线段的覆盖上,\(q\) 在第二条线段的覆盖上,并且 \(p, q\) 一个是另一个的祖先。证明是容易的,考察一个在两个的交集内的点不断往上找即可。
进一步地,我们可以这样来刻画这个关系,即:建两颗线段树,第一颗插入时将所有遍历到的点都进行插入,查询时只查询完全覆盖的点;第二颗插入时只插入完全覆盖的点,查询时查询所有遍历到的点。分别对应了 \(q\) 是 \(p\) 的祖先和 \(p\) 是 \(q\) 的祖先。
接着,将所有操作离线,我们发现本题的每一条直线实际上对应有两个维度,一个是存在的时间 \([l_1, r_1]\),另一个是包含它的集合 \([l_2, r_2]\),这个可以对每条直线卡一个 ODT 维护。然后我们发现一个查询实际上是要求与这两维限制都有交的所有直线在某一点处的最大值 \(x\)。那么我们先套两层线段树,外层是时间,内层是集合。具体来说,外层就像是线段树分治,接着我们遍历外层线段树上的每一个点,对于每个点再套一遍,直接做可以做到 \(O(n \log^3 n)\)(最内层可以再套一层李超树或者凸包),或者我们可以先对这个点内要插入的所有直线和询问的点进行预排序,单调栈维护凸包可以做到 \(O(n \log^2 n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, inf = 2e18;
int n, q, m, ans[N];
void chmax(int &x, int y){ x = (x < y ? y : x); }
struct line{
int k, b;
bool operator < (const line &y) const {
return (k == y.k ? b > y.b : k < y.k);
}
};
struct convex{
vector<line> st; int p = 0;
void clear(){ st.clear(); p = 0; }
bool chk(line x, line y, line z){ // if z can pop y
return (z.b - x.b) * (x.k - y.k) <= (y.b - x.b) * (x.k - z.k);
}
void ins(line x){
if(!st.empty() && st.back().k == x.k) return;
while(st.size() >= 2 && chk(st[st.size() - 2], st.back(), x)) st.pop_back();
st.push_back(x);
}
int qry(int x){
if(st.empty()) return -inf;
while(p + 1 < st.size() && st[p].b - st[p + 1].b < (st[p + 1].k - st[p].k) * x) ++p;
return st[p].k * x + st[p].b;
// return 0;
}
};
struct iSEG{
convex q[N << 2];
int ls[N << 2], rs[N << 2], tot = 0, rt;
void clr(int p, int pl, int pr){
q[p].clear();
if(pl == pr) return;
int mid = (pl + pr) >> 1;
if(ls[p]) clr(ls[p], pl, mid), ls[p] = 0;
if(rs[p]) clr(rs[p], mid + 1, pr), rs[p] = 0;
}
void clear(){ clr(rt, 1, n); tot = rt = 0;}
void ins(int L, int R, line x, bool typ, int &p, int pl = 1, int pr = n){
if(!p) p = ++tot;
if(L <= pl && R >= pr) return q[p].ins(x);
if(typ) q[p].ins(x);
int mid = (pl + pr) >> 1;
if(L <= mid) ins(L, R, x, typ, ls[p], pl, mid);
if(R > mid) ins(L, R, x, typ, rs[p], mid + 1, pr);
}
int qry(int L, int R, int x, bool typ, int p, int pl = 1, int pr = n){
if(!p) return -inf;
if(L <= pl && R >= pr) return q[p].qry(x);
int ret = -inf;
if(typ) ret = q[p].qry(x);
int mid = (pl + pr) >> 1;
if(L <= mid) chmax(ret, qry(L, R, x, typ, ls[p], pl, mid));
if(R > mid) chmax(ret, qry(L, R, x, typ, rs[p], mid + 1, pr));
return ret;
}
}tr;
struct oSEG{
typedef tuple<int, int, int, int> tpi;
typedef tuple<line, int, int> tli;
vector<tpi> q[N << 2];
vector<tli> l[N << 2];
int ls(int p){ return p << 1; }
int rs(int p){ return p << 1 | 1; }
void insq(int tl, int tr, bool typ, tpi x, int p = 1, int pl = 1, int pr = ::q){
if(tl <= pl && tr >= pr) return q[p].push_back(x);
if(typ) q[p].push_back(x);
int mid = (pl + pr) >> 1;
if(tl <= mid) insq(tl, tr, typ, x, ls(p), pl, mid);
if(tr > mid) insq(tl, tr, typ, x, rs(p), mid + 1, pr);
}
void insl(int tl, int tr, bool typ, tli x, int p = 1, int pl = 1, int pr = ::q){
if(tl <= pl && tr >= pr) return l[p].push_back(x);
if(typ) l[p].push_back(x);
int mid = (pl + pr) >> 1;
if(tl <= mid) insl(tl, tr, typ, x, ls(p), pl, mid);
if(tr > mid) insl(tl, tr, typ, x, rs(p), mid + 1, pr);
}
void work(int p = 1, int pl = 1, int pr = ::q){
sort(q[p].begin(), q[p].end()), sort(l[p].begin(), l[p].end());
for(int i : {0, 1}){
tr.clear();
for(auto [x, L, R] : l[p]) tr.ins(L, R, x, i, tr.rt);
for(auto [x, L, R, idx] : q[p]) chmax(ans[idx], tr.qry(L, R, x, i ^ 1, tr.rt));
}
if(pl == pr) return;
int mid = (pl + pr) >> 1;
work(ls(p), pl, mid), work(rs(p), mid + 1, pr);
}
}T[2];
struct ODT{
line li;
struct node{
int l, r, v;
bool operator < (const node &b) const {
return l < b.l;
}
};
set<node> s;
auto split(int x){
if(x > n) return s.end();
auto it = prev(s.upper_bound({x, 0, 0}));
auto [l, r, v] = *it;
if(x == l) return it;
s.erase(it);
s.insert({l, x - 1, v});
return s.insert({x, r, v}).first;
}
void ins(int t, int l, int r){
auto itr = split(r + 1), itl = split(l);
for(auto it = itl; it != itr; ++it){
int fr = it -> v;
if(fr){
for(int i : {0, 1})
T[i].insl(fr, t - 1, i, {li, it -> l, it -> r});
}
}
s.erase(itl, itr);
s.insert({l, r, t});
}
void era(int t, int l, int r){
auto itr = split(r + 1), itl = split(l);
for(auto it = itl; it != itr; ++it){
int fr = it -> v;
if(fr){
for(int i : {0, 1})
T[i].insl(fr, t, i, {li, it -> l, it -> r});
}
}
s.erase(itl, itr);
s.insert({l, r, 0});
}
}odt[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> q;
for(int i = 1; i <= m; ++i) cin >> odt[i].li.k >> odt[i].li.b, odt[i].s.insert({1, n, 0});
vector<int> rge;
for(int i = 1; i <= q; ++i){
int op;
cin >> op;
if(op == 1 || op == 2){
int l, r, x;
cin >> l >> r >> x;
if(op == 1) odt[x].ins(i, l, r);
else odt[x].era(i, l, r);
}
else{
int s, l, r, x;
cin >> s >> l >> r >> x;
rge.push_back(i); ans[i] = -inf;
for(int j : {0, 1})
T[j].insq(s, i, j ^ 1, {x, l, r, i});
}
}
for(int i = 1; i <= m; ++i) odt[i].era(q, 1, n);
for(int i : {0, 1}) T[i].work();
for(int i : rge){
if(ans[i] == -inf) cout << "-inf\n";
else cout << ans[i] << '\n';
}
return 0;
}

浙公网安备 33010602011771号