​「LibreOJ NOI Round #2」单枪匹马​​​

比较容易想到分子分母分开维护
省选模拟 19/10/11 (LibreOJ NOI Round #2)_git
好像可以矩阵乘
省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_02
然后就可以线段树维护了,省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_03 可过,但被学校的老爷机卡飞
考虑到只会从后面插点,是不是可以前缀和呢,常识构造一个逆矩阵
省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_04省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_05
然后就可以 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_06 做了
学到了矩阵也可以构造逆元…

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
const int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b;}
int mul(int a, int b){ return 1ll * a * b % Mod;}
void Add(int &a, int b){ a = add(a, b);}
int n, m, typ, a[N], ret;
int ansx, ansy;
struct matrix{
int a[2][2];
matrix(){ memset(a, 0, sizeof(a)); }
matrix operator * (const matrix &A){
matrix B; for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++) for(int k = 0; k < 2; k++)
Add(B.a[i][j], mul(a[i][k], A.a[k][j]));
return B;
}
} sum[N], inv[N];
int main(){
n = ret = read(), m = read(), typ = read();
sum[0].a[0][0] = sum[0].a[1][1] = inv[0].a[0][0] = inv[0].a[1][1] = 1;
for(int i = 1; i <= n; i++){
a[i] = read();
matrix A; A.a[0][0] = a[i]; A.a[0][1] = A.a[1][0] = 1;
matrix B; B.a[0][1] = B.a[1][0] = 1; B.a[1][1] = Mod - a[i];
sum[i] = sum[i-1] * A;
inv[i] = B * inv[i-1];
}
matrix res; res.a[0][0] = 1;
for(int i = 1; i <= m; i++){
int op = read();
if(op == 1){
int x = read();
if(typ == 1) x ^= (ansx ^ ansy);
a[++ret] = x;
matrix A; A.a[0][0] = x; A.a[0][1] = A.a[1][0] = 1;
matrix B; B.a[0][1] = B.a[1][0] = 1; B.a[1][1] = Mod - x;
sum[ret] = sum[ret-1] * A;
inv[ret] = B * inv[ret-1];
}
if(op == 2){
int l = read(), r = read();
if(typ == 1) l ^= (ansx ^ ansy), r ^= (ansx ^ ansy);
matrix now = inv[l-1] * sum[r] * res;
ansx = now.a[0][0], ansy = now.a[1][0];
cout << ansx << " " << ansy << '\n';
}
} return 0;
}

​「LibreOJ NOI Round #2」黄金矿工​

​​ 首先费用流的暴力可以拿省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_07,如果你足够优秀学过消环之类的玄学东西的话好像可以拿不错的分数
先简化一下询问,省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_08,把 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_09 改成 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_10, 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_11 改成 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_12 即可
我们需要给一个点反悔的机会
考虑到如果一个矿工匹配了一个已经匹配过的金矿,这个矿工与另一个矿工也联通了
记原来的矿工为省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_13,当前为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_14,贡献 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_15
再转念一想,相当于省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_14 与一块在当前点权值为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_17 的金子匹配
然后这边匹配的金子也应该有反悔空间,如果有一个金子强了它的矿工,那么相当与那块金子权值为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_18 的矿工匹配,于是我们每次找到最大的金子/矿工匹配之后,在金子的位置放一个 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_18 的矿工,在矿工的位置放一个 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_18 的金子
然后从上往下走看做把边的流量加1,如果这条边有流量,就可以向上走表示退流
然后可以每个点开一个堆表示这个点的矿工或者金子
其实主要的思想是:不真正去退流,而是转换条件在不退流的情况下实现退流的意义
暴力的复杂度 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_21

// 60 pts
#include<bits/stdc++.h>
#define N 100050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
int n, m;
vector<int> v[N];
ll dis[N], ans; int dep[N], fa[N], cnt[N];
int home, Max;
void dfs(int u){
for(int i = 0; i < v[u].size(); i++){
int t = v[u][i];
dis[t] += dis[u]; fa[t] = u; dep[t] = dep[u] + 1; dfs(t);
}
}
priority_queue <int> mine[N], gold[N];
void findgold(int u, int las, int inc){
if(!gold[u].empty() && gold[u].top() + inc > Max){
Max = gold[u].top() + inc;
home = u;
}
if(fa[u] != las && cnt[u]) findgold(fa[u], u, inc);
for(int i = 0; i < v[u].size(); i++){
int t = v[u][i]; if(t != las) findgold(t, u, inc);
}
}
void findmine(int u, int las, int inc){
if(!mine[u].empty() && mine[u].top() + inc > Max){
Max = mine[u].top() + inc;
home = u;
}
if(fa[u] != las) findmine(fa[u], u, inc);
for(int i = 0; i < v[u].size(); i++){
int t = v[u][i]; if(t != las && cnt[t]) findmine(t, u, inc);
}
}
void move(int x, int y){
while(x^y){
if(dep[y] > dep[x]) ++cnt[y], y = fa[y];
else --cnt[x], x = fa[x];
}
}
void Mine(int u, int v){
home = Max = 0;
int val = v - dis[u];
findgold(u, 0, val);
if(home){
move(u, home);
ans += (ll)Max;
gold[home].pop();
gold[u].push(-val);
mine[home].push(- (Max - val));
}
else mine[u].push(val);
}
void Gold(int u, int v){
home = Max = 0;
int val = v + dis[u];
findmine(u, 0, val);
if(home){
move(home, u);
ans += (ll)Max;
mine[home].pop();
mine[u].push(-val);
gold[home].push(- (Max - val));
}
else gold[u].push(val);
}
int main(){
n = read(), m = read();
for(int i = 1; i < n; i++){
int x = read(), y = read(), z = read();
v[x].push_back(y); dis[y] = z;
} dep[1] = 1; dfs(1);
for(int i = 1; i <= m; i++){
int op = read(), u = read(), v = read();
if(op == 1) Mine(u, v);
if(op == 2) Gold(u, v);
cout << ans << '\n';
} return 0;
}

考虑用数据结构模拟这个过程
首先要用一棵线段树维护流量,支持链加,查询最后一个流量 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_22 即不能流的位置
可以维护区间最小然后在线段树上二分
然后加矿工的时候需要退流到最上面的点,最上面的点的子树都可以走到,于是对金矿维护子树最大值及位置,加金矿的时候,对它有贡献的矿工存在的地方是它到根的一条链加上这个这条链上所有点能往下退流到的所有点,发现这个并不好直接维护,我们考虑把矿工的贡献在修改矿工的时候加到金矿里
对于每个金矿,维护一个省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_23之类的东西,当插入矿工时,将矿工能退流到的所有金矿的省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_23 插入这个矿工,显然暴力插要凉,考虑链分治,一个点维护它的轻儿子的矿工的最大值,跳链的时候改一下即可
整理一下繁琐的细节:
1.我们需要三棵线段树
一棵维护流量,支持区间加区间最小区间二分第一个小于等于0的位置
一棵维护子树黄金的最大值,线段树中还需对每一开一个 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_23,支持区间查询
一棵维护每个点轻儿子的最大值,同时支持区间查询
2.每次加减流量或加减矿工只后,要重新把矿工跳一遍,更新矿工对金矿的贡献
然后模拟即可…,代码抄的 ​​​zxyoi​

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
#define pi pair<int,int>
#define cs const
#define mp make_pair
#define fi first
#define se second
const int INF = 0x7f7f7f7f;
cs pi unit = mp(-INF, 0);
typedef long long ll;
int n, m; ll ans;
vector<int> v[N];
ll dis[N];
int dep[N], fa[N], siz[N], son[N], in[N], out[N], nd[N], bot[N], top[N], sign;
pi las[N];
void dfs(int u){
siz[u] = 1;
for(int i = 0; i < v[u].size(); i++){
int t = v[u][i]; fa[t] = u; dis[t] += dis[u];
dep[t] = dep[u] + 1; dfs(t); siz[u] += siz[t];
if(siz[t] > siz[son[u]]) son[u] = t;
}
}
void dfs2(int u, int Top){
top[u] = Top; in[u] = ++sign; nd[sign] = u;
if(son[u]) dfs2(son[u], Top), bot[u] = bot[son[u]];
else{ bot[u] = u; las[Top] = unit; }
for(int i = 0; i < v[u].size(); i++) if(v[u][i]^son[u]) dfs2(v[u][i], v[u][i]);
out[u] = sign;
}
struct SGT{
int mn[N << 2], tg[N << 2];
void pushup(int x){ mn[x] = min(mn[x<<1], mn[x<<1|1]);}
void pushtg(int x, int v){ tg[x] += v; mn[x] += v;}
void pushdown(int x){ if(tg[x]) pushtg(x<<1, tg[x]), pushtg(x<<1|1, tg[x]), tg[x] = 0;}
void add(int x, int l, int r, int L, int R, int v){
if(L<=l && r<=R){ pushtg(x, v); return; }
pushdown(x); int mid = (l+r) >> 1;
if(L<=mid) add(x<<1, l, mid, L, R, v);
if(R>mid) add(x<<1|1, mid+1, r, L, R, v);
pushup(x);
}
int queryl(int x, int l, int r, int L, int R){
if(L>R || mn[x] > 0) return -1;
int mid = (l+r) >> 1;
if(L<=l && r<=R){
if(l == r) return l; pushdown(x);
int res = queryl(x<<1, l, mid, L, R);
if(res == -1) res = queryl(x<<1|1, mid+1, r, L, R);
return res;
} pushdown(x);
int res = -1;
if(L<=mid) res = queryl(x<<1, l, mid, L, R);
if(R>mid && res == -1) res = queryl(x<<1|1, mid+1, r, L, R);
return res;
}
int queryr(int x, int l, int r, int L, int R){
if(L>R || mn[x] > 0) return -1;
int mid = (l+r) >> 1;
if(L<=l && r<=R){
if(l == r) return l;
pushdown(x); int res = queryr(x<<1|1, mid+1, r, L, R);
if(res == -1) res = queryr(x<<1, l, mid, L, R);
return res;
} pushdown(x); int res = -1;
if(R>mid) res = queryr(x<<1|1, mid+1, r, L, R);
if(L<=mid && res == -1) res = queryr(x<<1, l, mid, L, R);
return res;
}
void update(int L, int R, int v){ add(1, 1, n, L, R, v);}
int Ql(int L, int R){ return queryl(1, 1, n, L, R);}
int Qr(int L, int R){ return queryr(1, 1, n, L, R);}
} t1;
struct Segmentree{
pi mx[N << 2]; set<pi > st[N << 2];
void pushup(int x){ mx[x] = max(mx[x<<1], mx[x<<1|1]);}
void build(int x, int l, int r){
if(l == r){ mx[x] = unit; return;}
int mid = (l+r) >> 1; build(x<<1, l, mid); build(x<<1|1, mid+1, r);
pushup(x);
}
void modify(int x, int l, int r, int p, pi pre, pi now){
if(l == r){
if(pre.fi != INF) st[x].erase(pre);
if(now.fi != INF) st[x].insert(now);
if(st[x].empty()) mx[x] = mp(-INF, nd[l]);
else mx[x] = *st[x].rbegin();
return;
} int mid = (l+r) >> 1;
if(p <= mid) modify(x<<1, l, mid, p, pre, now);
else modify(x<<1|1, mid+1, r, p, pre, now);
pushup(x);
}
pi ask(int x, int l, int r, int L, int R){
if(L<=l && r<=R) return mx[x];
int mid = (l+r) >> 1;
if(R<=mid) return ask(x<<1, l, mid, L, R);
else if(L>mid) return ask(x<<1|1, mid+1, r, L, R);
else return max(ask(x<<1, l, mid, L, R), ask(x<<1|1, mid+1, r, L, R));
}
void update(int x, pi pre, pi now){ modify(1, 1, n, x, pre, now); }
pi query(int l, int r){ return ask(1, 1, n, l, r); }
}t2, t3;
void add(int u, int d){
while(top[u] != 1){
t1.update(in[top[u]], in[u], d);
u = fa[top[u]];
} if(u != 1) t1.update(2, in[u], d);
}
void modify(int u){
while(fa[top[u]]){
int t = t1.Ql(in[top[u]]+1, in[bot[u]]);
int v = t == -1 ? bot[u] : nd[t-1]; pi tp;
if(t1.Ql(in[top[u]], in[top[u]]) != -1) tp = unit;
else tp = t3.query(in[top[u]], in[v]);
t3.update(in[fa[top[u]]], las[top[u]], tp);
las[top[u]] = tp;
u = fa[top[u]];
}
}
void Mine(int u, int val){
int v = u;
for(int t = u, tp; t; t = fa[top[t]]){
tp = t1.Qr(in[top[t]], in[t]);
if(tp != -1){ v = nd[tp]; break;}
}
pi t = t2.query(in[v], out[v]);
if(t.fi + val < 0){
t3.update(in[u], unit, mp(val, u));
modify(u);
}
else{
ans += t.fi + val;
t2.update(in[t.se], t, unit); // del
t2.update(in[u], unit, mp(-val, u)); // add gold
t3.update(in[t.se], unit, mp(-t.fi, t.se)); // add mine
add(u, -1); add(t.se, 1); modify(u), modify(t.se);
}
}
void Gold(int u, int val){
pi p = unit;
for(int t = u, v, tp; t; t = fa[top[t]]){
tp = t1.Ql(in[t] + 1, in[bot[t]]);
v = tp == -1 ? bot[t] : nd[tp - 1];
p = max(p, t3.query(in[top[t]], in[v]));
}
if(p.fi + val < 0){
t2.update(in[u], unit, mp(val, u));
}
else{
ans += p.fi + val;
t3.update(in[u], unit, mp(-val, u));
t3.update(in[p.se], p, unit);
t2.update(in[p.se], unit, mp(-p.fi, p.se));
add(p.se, -1); add(u, 1); modify(p.se); modify(u);
}
}
int main(){
n = read(), m = read();
for(int i = 1; i < n; i++){
int x = read(), y = read(), z = read();
v[x].push_back(y); dis[y] = z;
} dep[1] = 1; dfs(1); dfs2(1, 1);
t2.build(1, 1, n); t3.build(1, 1, n);
for(int i = 1; i <= m; i++){
int op = read(), u = read(), v = read();
if(op == 1) Mine(u, v - dis[u]);
if(op == 2) Gold(u, v + dis[u]);
cout << ans << '\n';
} return 0;
}

​「LibreOJ NOI Round #2」不等关系​

​​ 首先有状压 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_26 表示选的集合为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_27,最后一个点为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_28 的方案数
考虑到选了哪些点并没有关系,我们只需要维护相对大小
省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_29 表示到第省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_28个点,排名为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_31 的方案数,每次填入 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_31 过后将原来排名 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_33 的全部加1
省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_34省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_35
省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_37
前缀和优化转移即可
这个方法好像不太能优化,考虑容斥
先保证 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_34 忽略 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36,然后把不满足 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36 的容斥掉
考虑到最后大于的情况是 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_41
如果我们令满足限制为 1,不满足为 0
求的是省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_42 的方案数
考虑到 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36 的限制不好满足,但这种方案很好算 ,就是 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_44
这样的话 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_45 前面和 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_45 后面就可以没有干扰
省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_47
枚举 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_45 的位置省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_31,令 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_50 为 i 的方案数,位置 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_31 前面的方案数就是 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_52
然后要强制后面的全部是 0,就只有唯一的一种单调上升的排列
然而前 j 个的方案数只是一种相对顺序,i 的方案中还要对前 j 个重新分配标号
省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_53 表示 省选模拟 19/10/11 (LibreOJ NOI Round #2)_git_28 的前缀 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36 出现次数
省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_56
组合数拆出来分治 省选模拟 19/10/11 (LibreOJ NOI Round #2)_#define_57 即可
主要思想:省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_36 分界处的条件不好处理,我们直接把它钦定为 省选模拟 19/10/11 (LibreOJ NOI Round #2)_i++_45,然后容斥减去不合法的方案

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
const int Mod = 998244353, G = 3;
const int C = 19;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b;}
int mul(int a, int b){ return 1ll * a * b % Mod;}
void Add(int &a, int b){ a = a + b >= Mod ? a + b - Mod : a + b;}
int power(int a, int b){ int ans = 1; for(;b;b>>=1, a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans;}
int n; char S[N];
#define poly vector<int>
poly w[C + 1];
int fac[N], inv[N], ifac[N], f[N];
void prework(){
for(int i = 1; i <= C; i++) w[i].resize(1 << i-1);
int wn = power(G, (Mod-1) / (1<<C));
w[C][0] = 1;
for(int i = 1; i < (1<<C-1); i++) w[C][i] = mul(wn, w[C][i-1]);
for(int i = C-1; i; i--) for(int j = 0; j < (1 << i-1); j++) w[i][j] = w[i+1][j << 1];
fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
for(int i = 2; i <= n; i++){
fac[i] = mul(fac[i-1], i);
inv[i] = mul(Mod-Mod/i, inv[Mod%i]);
ifac[i] = mul(ifac[i-1], inv[i]);
}
}
int rev[N << 4], bit, up;
void init(int len){ bit = 0; up = 1; while(up < len) up <<= 1, bit++;
for(int i = 0; i < up; i++) rev[i] = (rev[i>>1]>>1) | ((i&1) << (bit-1));
}
int cnt[N];
void NTT(poly &a, int flg){
for(int i = 0; i < up; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
for(int i = 1, l = 1; i < up; i <<= 1, ++l)
for(int j = 0; j < up; j += (i<<1))
for(int k = 0; k < i; k++){
int x = a[k+j], y = mul(w[l][k], a[k+j+i]);
a[k+j] = add(x, y); a[k+j+i] = add(x, Mod-y);
}
if(flg == -1){
reverse(a.begin() + 1, a.end()); int iv = power(up, Mod - 2);
for(int i = 0; i < up; i++) a[i] = mul(a[i], iv);
}
}
poly operator * (poly a, poly b){
int len = a.size() + b.size() - 1;
init(len); a.resize(up); b.resize(up);
NTT(a, 1); NTT(b, 1);
for(int i = 0; i < up; i++) a[i] = mul(a[i], b[i]);
NTT(a, -1); a.resize(len); return a;
}
#define pb push_back
void cdq(int l, int r){
if(l == r) return;
int mid = (l+r) >> 1;
cdq(l, mid);
poly a, b;
a.pb(0); b.pb(0);
for(int i = 1; i <= r-l+1; i++) a.pb(ifac[i]);
for(int i = l; i <= mid; i++) b.pb((S[i] == '<') ? 0 : ((cnt[i] & 1) ? Mod - f[i] : f[i]));
a = a * b;
for(int i = mid + 1; i <= r; i++)
Add(f[i], mul(((cnt[i-1] & 1) ? Mod - 1: 1), a[i-l+1]));
cdq(mid + 1, r);
}
int main(){
scanf("%s", S + 1);
n = strlen(S + 1) + 1;
for(int i = 1; i < n; i++) cnt[i] = cnt[i-1] + (S[i] == '>');
prework(); f[0] = 1; cdq(0, n);
cout << mul(f[n], fac[n]); return 0;
}