省选联测 1 - 20
2
A. 集合划分
分治,设 \(f_{l, r, 1 / 0, 1 / 0, x}\) 表示区间 \([l, r]\) 左边右边为 \(S / T\) 集合,选择了 \(x\) 个 \(S\) 集合的方案数
合并区间发现\(1 / 0\)两维是一个矩阵乘法的形式,转移时候只转移合法的即可
最后一维的转移是卷积,直接 \(ntt\) 即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
const int mod = 998244353;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int n, a[maxn][2];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
int W[maxn], Cw[maxn], rev[maxn];
int deg, lg;
void Init(int len) {
deg = 1, lg = 0;
while (deg < len) deg <<= 1, ++lg;
W[0] = Cw[0] = 1;
int Wp = qpow(3, (mod - 1) / deg), Cp = qpow(qpow(3, mod - 2), (mod - 1) / deg);
for (int i = 1; i < deg; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lg - 1)), W[i] = 1LL * W[i - 1] * Wp % mod,
Cw[i] = 1LL * Cw[i - 1] * Cp % mod;
}
struct poly{
vector<int> f;
int &operator[](const int &i) { return f[i]; }
int operator[](const int &i) const { return f[i]; }
int Deg() { return f.size(); }
int Deg() const { return f.size(); }
void set(int len) { f.resize(len); }
void Adjust() {
while (f.size() > 1 && f.back() == 0) f.pop_back();
}
void Reverse() { reverse(f.begin(), f.end()); }
void Print() {
for (auto it : f) cerr << it << " ";
cerr << "\n";
}
void NTT(int deg, int w[], int opt) {
set(deg);
for (int i = 0; i < deg; ++i)
if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int t = deg >> 1, m = 1; m < deg; m <<= 1, t >>= 1)
for (int i = 0; i < deg; i += (m << 1))
for (int j = 0; j < m; ++j) {
int x = f[i + j], y = 1LL * w[t * j] * f[i + j + m] % mod;
f[i + j] = (x + y) % mod, f[i + j + m] = (x - y + mod) % mod;
}
if (opt == -1) {
int inv = ::qpow(deg, mod - 2);
for (int i = 0; i < deg; ++i) f[i] = 1LL * f[i] * inv % mod;
}
}
friend poly operator+(const poly &x, const poly &y) {
poly res;
res.set(max(x.Deg(), y.Deg()));
for (int i = 0; i < x.Deg(); ++i) res[i] = x[i];
for (int i = 0; i < y.Deg(); ++i) res[i] = (res[i] + y[i]) % mod;
return res;
}
void operator+=(const poly &x) {
set(max(Deg(), x.Deg()));
for (int i = 0; i < x.Deg(); ++i) f[i] = (f[i] + x[i]) % mod;
}
friend poly operator*(const poly &x, const poly &y) {
poly res, A = x, B = y;
Init(A.Deg() + B.Deg() - 1);
A.NTT(deg, W, 1);
B.NTT(deg, W, 1);
res.set(deg);
for (int i = 0; i < deg; ++i) res[i] = 1LL * A[i] * B[i] % mod;
res.NTT(deg, Cw, -1);
res.Adjust();
return res;
}
void operator*=(const poly &x) {
poly A = x;
Init(Deg() + A.Deg() - 1);
NTT(deg, W, 1), A.NTT(deg, W, 1);
for (int i = 0; i < deg; ++i) f[i] = 1LL * f[i] * A[i] % mod;
NTT(deg, Cw, -1);
Adjust();
}
};
struct matrix{
poly f[2][2];
void build(){
f[0][0].set(2);
f[0][0][1] = 1;
f[1][1].set(1);
f[1][1][0] = 1;
}
}I, tmp;
matrix merge(const matrix &x, const matrix &y, const int &pos){
matrix ans;
for(int i = 0; i <= 1; ++i)
for(int j = 0; j <= 1; ++j)
for(int p = 0; p <= 1; ++p)
for(int q = 0; q <= 1; ++q)
if(a[pos][j] <= a[pos + 1][p])ans.f[i][q] += x.f[i][j] * y.f[p][q];
return ans;
}
matrix solve(int l, int r){
if(l == r)return I;
int mid = (l + r) >> 1;
return merge(solve(l, mid), solve(mid + 1, r), mid);
}
int main(){
freopen("divide.in","r",stdin);
freopen("divide.out","w",stdout);
n = read(); read(); I.build();
for(int i = 1; i <= n + n; ++i)a[i][0] = read();
for(int i = 1; i <= n + n; ++i)a[i][1] = read();
tmp = solve(1, n << 1);
int ans = 0;
for(int i = 0; i <= 1; ++i)for(int j = 0; j <= 1; ++j)if(tmp.f[i][j].Deg() > n)add(ans, tmp.f[i][j][n]);
printf("%d\n",ans);
return 0;
}
B. ACT4!⽆限回转!
计算几何,不会
C. 积木
要求 \(\sum C_{a_i + b_i + c_i + a_j + b_j + c_j}^{a_i + b_i + a_j + b_j}C_{a_i + b_i + a_j + b_j}^{a_i + a_j}\)
形如 \(c_{a + b + c}^{a + b}c_{a + b}^{a}\)
实际上就是三维坐标从 \(0\) 走到 \((a, b, c)\) 的方案数
初始 \((-a, -b, -c)\) 都设成 \(1\)
然后递推减去自己即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
const int mod = 1e8 + 7;
const int base = 151;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int n, f[305][305][305], fac[maxn], inv[maxn];
struct node{int a, b, c;}d[maxn];
int calc(int x, int y, int z){return 1ll * fac[x + y + z] * inv[x] % mod * inv[y] % mod * inv[z] % mod;}
int main(){
freopen("cyj.in","r",stdin);
freopen("cyj.out","w",stdout);
n = read();
fac[0] = inv[0] = 1; for(int i = 1; i <= 10000; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
inv[10000] = qpow(fac[10000], mod - 2); for(int i = 9999; i >= 1; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
for(int i = 1; i <= n; ++i)d[i].a = read(), d[i].b = read(), d[i].c = read();
for(int i = 1; i <= n; ++i)++f[base - d[i].a][base - d[i].b][base - d[i].c];
for(int i = 1; i <= 303; ++i)
for(int j = 1; j <= 303; ++j)
for(int k = 1; k <= 303; ++k)
f[i][j][k] = (f[i][j][k] + f[i - 1][j][k] + f[i][j - 1][k] + f[i][j][k - 1]) % mod;
int ans = 0;
for(int i = 1; i <= n; ++i)ans = (ans + f[base + d[i].a][base + d[i].b][base + d[i].c] - calc(d[i].a + d[i].a, d[i].b + d[i].b, d[i].c + d[i].c)) % mod;
ans = 1ll * ans * inv[2] % mod; ans = (ans % mod + mod) % mod;
printf("%d\n",ans);
return 0;
}
3
A. Arg
先考虑求 \(lis\) 每次找到每个长度结尾最小的值,然后二分
发现他们是单调的,于是只需要知道集合,那么就能还原数组
放到求方案这里,就再加一个是否选过的状态
用三进制进行装压即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int inf = 0x3f3f3f3f;
int n, m, a[25], p[25], ans, g[25];
ll f[20000005], base[25];
int main(){
freopen("arg.in","r",stdin);
freopen("arg.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= m; ++i)p[a[i] = read()] = i;
base[0] = 1; for(int i = 1; i <= n; ++i)base[i] = base[i - 1] * 3;
f[0] = 1;
for(int i = 0; i < base[n]; ++i){
int s = i;
for(int j = 1; j <= n; ++j){g[j] = s % 3; s /= 3;}
int len = 0, lis = 0;
for(int j = 1; j <= n; ++j)if(g[j]){
++len; lis += g[j] == 1;
}
if(len == n && lis == m){ans += f[i]; continue;}
int las = -1;
for(int j = n; j >= 1; --j)if(g[j]){
if(g[j] == 1)las = j;
continue;
}else{
if(p[j] > 1 && g[a[p[j] - 1]] == 0)continue;
if(las == -1)f[i + base[j - 1]] += f[i];
else f[i + base[j - 1] + base[las - 1]] += f[i];
}
}
printf("%d\n",ans);
return 0;
}
B. Bsh
考虑一条边,把图形分成两部分,对于其中的任意一部分,其内部点的最短距离一定不过另外一部分
两部分之间的最短距离一定过这条边上的两点
于是进行分治,每次尽可能平均的分成两部分
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 120005;
const int inf = 0x3f3f3f3f;
int Q;
struct query{int x, y, id;}qq[maxn], tq[maxn];
int ans[maxn];
int node[maxn];
int n, head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int id[maxn];
int ds[maxn], dt[maxn];
queue<int>q;
void bfs(int d[], int s){
q.push(s); d[s] = 0;
while(!q.empty()){
int x = q.front(); q.pop();
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(!id[v])continue;
if(d[v] > d[x] + 1){
d[v] = d[x] + 1;
q.push(v);
}
}
}
}
int tmp[maxn];
void solve(int l, int r, int ql, int qr){
if(l >= r || ql > qr)return;
if(r - l <= 2){
for(int i = ql; i <= qr; ++i)ans[qq[i].id] = qq[i].x != qq[i].y;
return;
}
int s = 0, t = 0, mx = -1;
for(int x = l; x <= r; ++x)id[node[x]] = x - l + 1;
for(int x = l; x <= r; ++x)
for(int i = head[node[x]]; i; i = e[i].net){
int v = e[i].to; if(id[v] > id[node[x]]){
int k = min(id[v] - id[node[x]], id[node[x]] - id[v] + r - l);
if(k > mx){ mx = k; s = node[x]; t = v;}
}
}
bfs(ds, s); bfs(dt, t);
for(int i = id[s]; i <= id[t]; ++i)tmp[i] = node[i + l - 1];
int mid = id[s] + l - 2;
for(int i = id[t] + l; i <= r; ++i)node[++mid] = node[i];
int pos = mid;
for(int i = id[s]; i <= id[t]; ++i)node[++pos] = tmp[i];
for(int i = l; i <= mid; ++i)id[node[i]] = 1;
for(int i = mid + 1; i <= r; ++i)id[node[i]] = 2;
pos = 0; int j = ql - 1;
for(int i = ql; i <= qr; ++i){
if(id[qq[i].x] + id[qq[i].y] == 3){
ans[qq[i].id] = min(min(ds[qq[i].x] + ds[qq[i].y], ds[qq[i].x] + dt[qq[i].y] + (s != t)), min(dt[qq[i].y] + dt[qq[i].x], ds[qq[i].y] + dt[qq[i].x] + (s != t)));
}else{
if(id[qq[i].x] == 1 && id[qq[i].y] == 1)qq[++j] = qq[i];
if(id[qq[i].x] == 2 && id[qq[i].y] == 2)tq[++pos] = qq[i];
}
}
for(int i = 1; i <= pos; ++i)qq[j + i] = tq[i];
for(int x = l; x <= r; ++x)id[node[x]] = 0, ds[node[x]] = dt[node[x]] = inf;
solve(mid + 1, r, j + 1, j + pos);
if(mid + 2 != r){
node[++mid] = s; node[++mid] = t;
sort(node + l, node + mid + 1);
}
solve(l, mid, ql, j);
}
int main(){
freopen("bsh.in","r",stdin);
freopen("bsh.out","w",stdout);
n = read(); add(1, n); add(n, 1);
for(int i = 1; i < n; ++i)add(i, i + 1), add(i + 1, i);
for(int i = 1; i <= n - 3; ++i){int u = read(), v = read(); add(u, v); add(v, u);}
Q = read(); for(int i = 1; i <= Q; ++i)qq[i].x = read(), qq[i].y = read(), qq[i].id = i;
for(int i = 1; i <= n; ++i)node[i] = i, ds[i] = dt[i] = inf;
solve(1, n, 1, Q);
for(int i = 1; i <= Q; ++i)printf("%d\n",ans[i]);
return 0;
}
C. Cti
网络流神奇建图,时间有点长不想写题解了
code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<cassert>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar(); bool f = false;
while(!isdigit(c)){f = c == '-'; c = getchar();}
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int inf = 0x3f3f3f3f;
const int maxn = 6005;
int n, m, mp[55][55];
int id(int x, int y){return (x - 1) * m + y;}
struct Dinic{
int head[maxn], now[maxn], tot = 1, s, t;
struct edge{int to, net, val;}e[maxn * 1000];
void add(int u, int v, int w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
e[tot].val = w;
}
void link(int u, int v, int w){
add(u, v, w); add(v, u, 0);}
int dep[maxn];
bool vis[maxn];
queue<int>q;
bool bfs(){
for(int i = 1; i <= t; ++i)dep[i] = 0;
while(!q.empty())q.pop();
dep[s] = 1; q.push(s); now[s] = head[s];
while(!q.empty()){
int x = q.front(); q.pop();
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(!dep[v] && e[i].val > 0){
dep[v] = dep[x] + 1;
now[v] = head[v];
if(v == t)return true;
q.push(v);
}
}
}
return false;
}
int dfs(int x, int from){
if(x == t || from <= 0)return from;
int res = from, i;
for(i = now[x]; i; i = e[i].net){
int v = e[i].to;
if(e[i].val > 0 && dep[v] == dep[x] + 1){
int k = dfs(v, min(res, e[i].val));
if(k <= 0)dep[v] = 0;
e[i].val -= k;
e[i ^ 1].val += k;
res -= k;
if(res <= 0)break;
}
}
now[x] = i;
return from - res;
}
int dinic(){
int ans = 0;
while(bfs())ans += dfs(s, inf);
return ans;
}
void init(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
mp[i][j] = read();
s = n * m * 2 + 1, t = s + 1;
int ans = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
link(id(i, j), n * m + id(i, j), inf);
int mx = 0;
if(mp[i][j] == -1){
for(int k = 1; k <= i; ++k)mx = max(mx, mp[k][j]);
for(int k = i - 1; k >= 1; --k)link(n * m + id(k, j), n * m + id(k + 1, j), mx - max(0, mp[k + 1][j]));
link(n * m + id(i, j), t, inf);
}
if(mp[i][j] == -2){
for(int k = i; k <= n; ++k)mx = max(mx, mp[k][j]);
for(int k = i + 1; k <= n; ++k)link(n * m + id(k, j), n * m + id(k - 1, j), mx - max(0, mp[k - 1][j]));
link(n * m + id(i, j), t, inf);
}
if(mp[i][j] == -3){
for(int k = 1; k <= j; ++k)mx = max(mx, mp[i][k]);
for(int k = j - 1; k >= 1; --k)link(id(i, k + 1), id(i, k), mx - max(0, mp[i][k + 1]));
link(s, id(i, j), inf);
}
if(mp[i][j] == -4){
for(int k = j; k <= m; ++k)mx = max(mx, mp[i][k]);
for(int k = j + 1; k <= m; ++k)link(id(i, k - 1), id(i, k), mx - max(0, mp[i][k - 1]));
link(s, id(i, j), inf);
}
ans += mx;
}
}
ans -= dinic();
printf("%d\n",ans);
}
}W;
int main(){
freopen("cti.in","r",stdin);
freopen("cti.out","w",stdout);
W.init();
return 0;
}
5
A. 小B的班级
显然单独考虑每条边的贡献
枚举一侧的男女生人数 \(x, y\)
贡献为 \(min(x, n - y) + min(n - x, y)\)
\(= min(x + y, n - x - y)\)
注意每个人是不同的,所以方案数为子树大小的人数次方,预处理一下就能 \(O(nm)\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 5005;
const int mod = 1e9 + 7;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int fac[maxn], inv[maxn];
void init(int n){
fac[0] = inv[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i >= 1; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
int n, m;
struct edge{int u, v, w;}e[maxn];
vector<int>g[maxn];
int si[maxn];
void dfs(int x, int fa){
si[x] = 1;
for(int v : g[x])if(v != fa)dfs(v, x), si[x] += si[v];
}
int c(int n, int m){return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;}
int cnt[maxn];
int p1[maxn], p2[maxn];
int main(){
n = read(), m = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read(), w = read();
e[i] = {u, v, w}; g[u].push_back(v); g[v].push_back(u);
}
dfs(1, 1); init(5000);
for(int i = 1; i < n; ++i){
int x = min(si[e[i].u], si[e[i].v]);
x = min(x, n - x);
cnt[x] = (cnt[x] + e[i].w) % mod;
}
int ans = 0;
for(int i = 1; i <= n; ++i)if(cnt[i]){
int res = 0;
p1[0] = p2[0] = 1;
for(int j = 1; j <= m + m; ++j)p1[j] = 1ll * p1[j - 1] * i % mod;
for(int j = 1; j <= m + m; ++j)p2[j] = 1ll * p2[j - 1] * (n - i) % mod;
for(int j = 1; j <= m; ++j)res = (res + 1ll * p1[j] * p2[m + m - j] % mod * j % mod * c(m + m, j)) % mod;
for(int j = m + 1; j <= m + m; ++j)res = (res + 1ll * p1[j] * p2[m + m - j] % mod * (m + m - j) % mod * c(m + m, j)) % mod;
ans = (ans + 1ll * res * cnt[i]) % mod;
}
printf("%d\n",ans);
return 0;
}
B. 小B的环
把原串倍长,转成链
把相邻字符相同的位置断开,分成若干段,此时只需在每一段内考虑首尾不同的限制
枚举长度,若该长度不可行那么有 \(a[1..n − k] = a[k + 1..n]\) 用字符串 \(hash\) 判断
即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 10000005;
const int base = 27;
char s[maxn];
ull hs[maxn], bp[maxn];
int ans[maxn];
ull get(int l, int r){return hs[r] - hs[l - 1] * bp[r - l + 1];}
void sol(){
int n = strlen(s + 1);
for(int i = 1; i <= n; ++i)s[i + n] = s[i];
bp[0] = 1; for(int i = 1; i <= n + n; ++i)bp[i] = bp[i - 1] * base;
for(int i = 1; i <= n + n; ++i)hs[i] = 1ull * hs[i - 1] * base + s[i] - 'a' + 1;
for(int i = 0; i <= n; ++i)ans[i] = 0;
int las = 1;
for(int i = 1; i <= n + n; ++i)if(i == n + n || s[i] == s[i + 1]){
for(int j = 1; j <= min(n, i - las + 1); ++j)if(get(las, i - j + 1) != get(las + j - 1, i))ans[j] = 1;
las = i + 1;
}
for(int i = n; i >= 1; --i)printf("%d",ans[i]);
printf("\n");
}
int main(){
freopen("loop.in","r",stdin);
freopen("loop.out","w",stdout);
while(~scanf("%s",s + 1))sol();
return 0;
}
C. 小B的农场
答案至少为 \(max(W, H) ∗ 2 + 2\)
所以最后选择的正方形一定经过直线 \(x = W / 2\) 或直线 \(y = H/2\),于是只要考虑这两种情况即可
对于我们最后选择的正方形,它四条边上一定都有点
考虑把选定的轴看做穿过矩形上下边的中轴
因此我们从上到下枚举矩形下边,用线段树维护矩形的上边的答案
此时左右边距离中轴距离单调,用单调栈维护
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 5e5 + 55;
int w, h, n;
struct node{
int x, y;
friend bool operator < (const node &x, const node &y){
return x.y == y.y ? x.x < y.x : x.y < y.y;
}
}d[maxn];
int ans;
struct seg{
struct node{int tag, val;}t[maxn << 2 | 1];
void built(int x, int l, int r){
t[x].tag = 0; t[x].val = d[l].y;
if(l == r)return;
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
}
void modify(int x, int l, int r, int L, int R, int val){
// printf("%d %d %d %d %d %d\n",x, l, r, L, R, val);
if(L > R || R < l || L > r)return;
if(L <= l && r <= R){
t[x].tag += val;
t[x].val += val;
return;
}
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
t[x].val = min(t[x << 1].val, t[x << 1 | 1].val) + t[x].tag;
// printf("%d %d %d %d\n",x, l, r, t[x].val);
}
}T;
int st1[maxn], top1;
int st2[maxn], top2;
void solve(){
sort(d + 1, d + n + 1);
T.built(1, 1, n);
top1 = top2 = 0;
for(int i = 1; i <= n; ++i){
ans = max(ans, d[i].y - T.t[1].val);
// printf("%d %d %d\n", ans, d[i].y, T.t[1].val);
if(d[i].x <= w / 2){
T.modify(1, 1, n, st1[top1], i - 1, d[i].x);
while(top1 && d[st1[top1]].x < d[i].x)
T.modify(1, 1, n, st1[top1 - 1], st1[top1] - 1, d[i].x - d[st1[top1]].x), --top1;
st1[++top1] = i;
}else{
T.modify(1, 1, n, st2[top2], i - 1, w - d[i].x);
while(top2 && d[st2[top2]].x > d[i].x)
T.modify(1, 1, n, st2[top2 - 1], st2[top2] - 1, d[st2[top2]].x - d[i].x), --top2;
st2[++top2] = i;
}
// for(int i = 1; i <= top1; ++i)printf("%d ",st1[i]);printf("\n");
// for(int i = 1; i <= top1; ++i)printf("%d ",st1[i]);printf("\n");
T.modify(1, 1, n, i, i, -w);
}
}
int main(){
freopen("farm.in","r",stdin);
freopen("farm.out","w",stdout);
w = read(), h = read(), n = read();
for(int i = 1; i <= n; ++i)d[i].x = read(), d[i].y = read();
d[++n] = {0, 0}; d[++n] = {w, h}; solve();
for(int i = 1; i <= n; ++i)swap(d[i].x, d[i].y);
swap(w, h); solve();
printf("%d\n",ans + ans);
return 0;
}
9
A. 序列
模拟题
考虑序列末尾一定是最左端或者最右端
于是对他们一起考虑
如果有两种数,那么一定一左一右,而且可以继续确定前面连续的一段
如果只有一种数,那么左右都可以
方案数就是每次选择左右时候乘 \(2\) ,字典序最小的方案就是贪心搞
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1005;
const int mod = 1e9 + 7;
int mp[maxn][maxn], pos[maxn][maxn];
int n, m, ans[maxn], cnt, l[maxn], r[maxn];
int L, R;
bool same(int pos){
bool ans = true;
for(int i = 2; i <= n; ++i)if(mp[i][pos] != mp[1][pos])return false;
return true;
}
bool work(){
bool flag = true;
for(; flag;){
flag = false;
for(int i = 1; i <= n; ++i){
while(r[i] - l[i] > R - L){
if(mp[i][r[i] - l[i] + 1] == ans[l[i]])++l[i];
else if(mp[i][r[i] - l[i] + 1] == ans[r[i]])--r[i];
else{
flag = true;
if(l[i] == L && L <= R){
ans[L++] = mp[i][r[i] - l[i] + 1];
++l[i];
}else if(r[i] == R && L <= R){
ans[R--] = mp[i][r[i] - l[i] + 1];
--r[i];
}else return false;
}
}
}
}
return true;
}
void add(int ll, int rr, int lim){
if(ll > rr)return;
int mi = 0x3f3f3f3f, pos = -1;
for(int i = ll; i <= rr; ++i){
if(mp[1][i] < mi){mi = mp[1][i]; pos = i;}
}
if(mi < lim){
for(int i = rr; i > pos; --i)ans[R--] = mp[1][i];
ans[L++] = mi;
if(ll < pos)add(ll, pos - 1, lim);
}else{
for(int i = rr; i >= ll; --i)ans[R--] = mp[1][i];
}
}
bool sol(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
pos[i][mp[i][j] = read()] = j;
cnt = 1;
for(int i = 1; i <= n; ++i)l[i] = 1, r[i] = m, mp[i][0] = 0x3f3f3f3f;
for(int i = 1; i <= m; ++i)ans[i] = -1;
L = 1, R = m;
while(L <= R){
int num1 = mp[1][R - L + 1], num2 = -1;
for(int i = 1; i <= n; ++i)if(mp[i][R - L + 1] != num1){
if(num2 == -1)num2 = mp[i][R - L + 1];
else if(num2 != mp[i][R - L + 1])return false;
}
if(num2 == -1){
int ll = R - L + 1;
while(ll > 1 && same(ll - 1)) --ll;
int lim = 0x3f3f3f3f;
for(int i = 1; i <= n; ++i)lim = min(lim, mp[i][ll - 1]);
for(int i = max(2, ll); i <= R - L + 1; ++i){cnt = (cnt + cnt);if(cnt >= mod)cnt -= mod;}
add(ll, R - L + 1, lim);
}else{
ans[L] = num1, ans[R] = num2;
if(num1 > num2)swap(ans[L], ans[R]);
++L; --R;
cnt = (cnt + cnt); if(cnt >= mod)cnt -= mod;
}
if(!work())return false;
}
return true;
}
int main(){
// freopen("array.in","r",stdin);
// freopen("array.out","w",stdout);
int t = read(); for(int i = 1; i <= t; ++i)if(sol()){
printf("%d\n",cnt);
for(int j = 1; j <= m; ++j)printf("%d ",ans[j]); printf("\n");
}else printf("0\n");
return 0;
}
B. 树
如果树的形态确定,那么取最大的若干个数作为 \(1 - n\) 路径上的边权即可得到答案
考虑维护这若干个数
首先 \(1 - n\) 边数的上界为出现数的种类数,初始我们每个数都取一个,此时对于剩下的数,一部分是我们没有用到的点的父亲,另外的是边权
那么贪心的用小的数做父亲即可
然后考虑截掉一个点,缩短边数,发现最优情况是去掉权值最大的点
但是需要注意的是小于等于 \(i\) 的数有 \(i\) 个时, \(i + 1\) 一定是 \(n\) 的祖先,因为 \(2 - i + 1\) 的父亲只能从他们中选择
显然如果数量不够一定无解
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 2e5 + 55;
bool flag[maxn];
int n, cnt[maxn];
ll ans[maxn];
priority_queue<ll, vector<ll>, greater<ll>>q;
void solve(){
for(int i = 0, sum = 0; i < n; ++i, sum += cnt[i])
if(sum < i)return;
else if(sum == i) flag[i + 1] = true;
int mxd = 0;
for(int i = 1; i <= n; ++i)mxd += (cnt[i] > 0);
ll res = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j < cnt[i]; ++j)q.push(i), res += i;
for(int dep = mxd, pos = n; dep; --dep){
while(q.size() > dep)res -= q.top(), q.pop();
ans[dep] = res;
while(pos && (flag[pos] || !cnt[pos]))--pos;
if(pos == 0)break;
res += pos; q.push(pos); --pos;
}
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n = read();
for(int i = 1; i <= n + n - 2; ++i)++cnt[read()];
for(int i = 1; i < n; ++i)ans[i] = -1;
solve();
for(int i = 1; i < n; ++i)printf("%lld ",ans[i]);
return 0;
}
C. 集合
不会做,直接按顺序加就有三四十
随机化乱跑能多水点
11
A. bot 的能量堆 energy
这是一道爆搜题。。。。。
维护 \(a, b - a\), 发现后者不会因为加减改变,于是每次操作可以加减,可以除一个 \(b - a\) 的质因数
除了直接减到 \(1\) 以外,其他加减一定为了变成某个质因数的倍数,于是枚举这个质因数就好了
状态数不多,直接搜就行
对于 \(a == b\) 我不知道为啥走到附近一个数直接不停的除掉质因子就行
感觉会有先除若干质因子,然后加/减接着继续除质因子这样的操作,但是构造出来总是存在上面那种方法的方案,甚至会更优秀。。。
不理解
题解的意思这部分也是爆搜
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int mx = 1000000;
const int maxn = mx + 555;
int a, b;
int prime[maxn], mi[maxn], cnt;
bool flag[maxn];
vector<int>v;
map<pii, int>mp;
int ans;
void dfs(int x, int y, int res){
if(res >= ans || (mp.count(pii(x, y)) && res >= mp[pii(x, y)]))return;
ans = min(ans, res + x - 1); mp[pii(x, y)] = res;
for(int p : v)if(y % p == 0){
if(x < p)ans = min(ans, res + p - x + 1);
else{
if((x % p) * 2 <= p)dfs(x / p, y / p, res + x % p + 1);
if((x % p) * 2 >= p)dfs(x / p + 1, y / p, res + p - x % p + 1);
}
}
}
int num(int x){
int res = 0;
for(int i = 1; prime[i] * prime[i] <= x && i <= cnt; ++i){
while(x % prime[i] == 0)++res, x /= prime[i];
}
if(x > 1) ++res; return res;
}
int get(int x){
int ans = x - 1;
for(int i = max(1, x - 10); i <= x + 10; ++i)ans = min(ans, num(i) + abs(x - i));
return ans;
}
void sol(){
cin >> a >> b; if(a > b)swap(a, b); b -= a;
if(b == 0){printf("%d\n", get(a));return;}
ans = 0x3f3f3f3f; v.resize(0); mp.clear(); int x = b;
for(int i = 1; i <= cnt && prime[i] * prime[i] <= x; ++i){
if(x % prime[i] == 0)v.push_back(prime[i]);
while(x % prime[i] == 0)x /= prime[i];
}
if(x > 1)v.push_back(x);
dfs(a, b, 0); printf("%d\n",ans);
}
int main(){
freopen("energy.in","r",stdin);
freopen("energy.out","w",stdout);
for(int i = 2; i <= mx; ++i){
if(!flag[i])prime[++cnt] = i, mi[i] = i;
for(int j = 1; j <= cnt && i * prime[j] <= mx; ++j){
flag[i * prime[j]] = 1;
mi[i * prime[j]] = prime[j];
if(i % prime[j] == 0)break;
}
}
int t = read(); for(int i = 1; i <= t; ++i)sol();
return 0;
}
B. bot 的矩阵 matrix
\(mmp\) 多测清空出了个神奇的锅
直接网络流稍微卡点常就能过
初始都放极小值,这样流量都为正
能选数的格子从行向列连极大值的边
源点到行连需要的减去已经放上的, 列到汇点也是
正解
首先可以判断最无脑的无解,即行列的和不相等。现在来考虑一下没有 个限制的情况。这里有一个很重要的点,第 i 行与第 j列只会在 (i, j) 位置有相互的影响,那么整个网格图可以建出一张二分图,若我们只从这个二分图中抽出一个生成树,从叶子开始满足每一个行/列的限制,最后只要满足整棵树行列和相等即可。加入m个限制也就是让树变成森林而已
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
namespace IO{
const int sz=1<<22;
char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
inline char gc(){
return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
}
template<class T> void read(T& x){
x=0; int f=1;char c=gc();
if(c=='-')f=-1;
for(;c<'0'||c>'9';c=gc())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=gc())
x=x*10+(c-'0');
x=x*f;
}
inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
template<class T> void print(T x,char c='\n'){
if(x<0)pc('-'),x=-x;
if(x==0) pc('0'); int t=0;
for(;x;x/=10) p[++t]=x%10+'0';
for(;t;--t) pc(p[t]); pc(c);
}
struct F{~F(){flush();}}f;
}
using IO::read;
using IO::print;
using IO::pc;
#define NO {pc('N');pc('o');pc('S');pc('o');pc('l');pc('u');pc('t');pc('i');pc('o');pc('n');pc('!');pc('\n');}
#define YES {pc('B');pc('o');pc('t');pc('a');pc('n');pc('y');pc('!');pc('\n');}
const int maxn = 4005;
const ll inf = 1e12;
struct Dinic{
int head[maxn], now[maxn], tot = 1, s, t;
struct edge{int to, net; ll val;}e[maxn * maxn];
void add(int u, int v, ll w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
e[tot].val = w;
}
void link(int u, int v, ll w){
add(u, v, w); add(v, u, 0);
}
int dep[maxn]; queue<int>q;
bool bfs(){
for(int i = 1; i <= t; ++i)dep[i] = 0;
while(!q.empty())q.pop();
dep[s] = 1; q.push(s); now[s] = head[s];
while(!q.empty()){
int x = q.front(); q.pop();
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(!dep[v] && e[i].val > 0){
dep[v] = dep[x] + 1;
now[v] = head[v];
if(v == t)return true;
q.push(v);
}
}
}
return false;
}
ll dfs(int x, ll from){
if(x == t || from <= 0)return from;
ll res = from; int i;
for(i = now[x]; i; i = e[i].net){
int v = e[i].to;
if(e[i].val > 0 && dep[v] == dep[x] + 1){
ll k = dfs(v, min(res, e[i].val));
if(k <= 0)dep[v] = 0;
e[i].val -= k;
e[i ^ 1].val += k;
res -= k;
if(res <= 0)break;
}
}
now[x] = i;
return from - res;
}
ll dinic(){
while(bfs())dfs(s, 4e18);
return 0;
}
ll mp[2005][2005], a[2005], b[2005];
int n, m, id[2005][2005], cnt;
void clear(){
for(int i = 1; i <= t; ++i)head[i] = now[i] = 0;
s = t = 0; cnt = 0; tot = 1;
}
void init(){
read(n); read(m); ll suma = 0, sumb = 0;
for(int i = 1; i <= n; ++i)read(a[i]);
for(int i = 1; i <= n; ++i)read(b[i]);
for(int i = 1; i <= n; ++i)suma += a[i], sumb += b[i];
if(suma != sumb){NO; return;}
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
mp[i][j] = -inf;
for(int i = 1, x, y, z; i <= m; ++i){
read(x); read(y); read(z);
mp[x][y] = z;
}
clear();
s = n + n + 1, t = s + 1;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j){
a[i] -= mp[i][j]; b[j] -= mp[i][j];
if(mp[i][j] == -inf){
link(i, j + n, inf + inf);
id[i][j] = tot;
}
}
for(int i = 1; i <= n; ++i)if(a[i] < 0 || b[i] < 0){NO; return;}
for(int i = 1; i <= n; ++i)link(s, i, a[i]);
for(int i = 1; i <= n; ++i)link(i + n, t, b[i]);
dinic();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if(mp[i][j] == -inf){
mp[i][j] = e[id[i][j]].val;
a[i] -= mp[i][j]; b[j] -= mp[i][j];
mp[i][j] -= inf;
}
for(int i = 1; i <= n; ++i)if(a[i] != 0 || b[i] != 0){NO; return;}
YES;
for(int i = 1; i <= n; ++i, pc('\n'))
for(int j = 1; j <= n; ++j)
print(mp[i][j], ' ');
}
}W;
int main(){
// freopen("matrix.in","r",stdin);
// freopen("matrix.out","w",stdout);
int t; read(t);
for(int i = 1; i <= t; ++i)W.init();
IO::flush();
return 0;
}
C. bot 的字符串 palindrome
比较神奇
设 \(f_{l, r, 0 / 1, len}\) 表示处理了 \(l - r\) 并且选择了 \(l, r\) 左侧 / 右侧多出 \(len\) 长度的方案数
进行前缀和优化即可
下面的代码是∑的,某大佬的写法
定义改成处理了 \(0 - l 和 r - n + 1\) 区间, 选择了\(l ,r\) 左侧 / 右侧多出 \(len\) 长度的方案数
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 505;
const ull base = 29;
const int mod = 1e9 + 7;
vector<int>f[maxn][maxn][2], g[maxn][maxn][2];
vector<ull>hl[maxn], hr[maxn];
ull bp[200005];
string s[maxn];
int n, ans, si[maxn];
ull get_hash(int i, int l, int r){
if(l <= r) return hl[i][r] - hl[i][l - 1] * bp[r - l + 1];
swap(l, r);return hr[i][l] - hr[i][r + 1] * bp[r - l + 1];
}
int main(){
freopen("palindrome.in","r",stdin);
freopen("palindrome.out","w",stdout);
int mx = 0; cin >> n;
for(int i = 1; i <= n; ++i)cin >> s[i], si[i] = s[i].size();
for(int i = 1; i <= n; ++i)mx = max(si[i], mx);
bp[0] = 1; for(int i = 1; i <= mx; ++i)bp[i] = bp[i - 1] * base;
for(int i = 1; i <= n; ++i){
hl[i].resize(si[i] + 2); hr[i].resize(si[i] + 2);
for(int j = 0; j <= si[i] - 1; ++j)hl[i][j + 1] = hl[i][j] * base + (s[i][j] - 'a' + 1);
for(int j = si[i] - 1; j >= 0; --j)hr[i][j + 1] = hr[i][j + 2] * base + (s[i][j] - 'a' + 1);
}
for(int i = 0; i <= n; ++i)
for(int j = i + 1; j <= n + 1; ++j){
f[i][j][0].resize(si[i] + 1, -1); f[i][j][1].resize(si[j] + 1, -1);
g[i][j][0].resize(si[i] + 1, -1); g[i][j][1].resize(si[j] + 1, -1);
}
for(int i = 1; i <= n + 1; ++i){
for(int l = 0, r = l + i; r <= n + 1; ++l, ++r){
for(int opt = 0, mx; opt <= 1; ++opt){
mx = opt ? si[r] : si[l];
for(int len = 0; len <= mx; ++len){
int res = 0, j;
if(opt || len == 0){
res = (g[l + 1][r][opt].size() <= len ? 0 : g[l + 1][r][opt][len]);
j = l + 1;
}else{
res = (g[l][r - 1][opt].size() <= len ? 0 : g[l][r - 1][opt][len]);
j = r - 1;
}
if(j > l && j < r){
if(len == 0)res = (res + f[j][r][0][si[j]]) % mod;
else{
if(opt){
if(si[j] <= len){
if(get_hash(j, 1, si[j]) == get_hash(r, len, len - si[j] + 1)){
res = (res + f[j][r][1][len - si[j]]) % mod;
}
}else{
if(get_hash(j, 1, len) == get_hash(r, len, 1)){
res = (res + f[j][r][0][si[j] - len]) % mod;
}
}
}else{
if(si[j] <= len){
if(get_hash(l, si[l] - len + 1, si[l] - len + si[j]) == get_hash(j, si[j], 1)){
res = (res + f[l][j][0][len - si[j]]) % mod;
}
}else{
if(get_hash(l, si[l] - len + 1, si[l]) == get_hash(j, si[j], si[j] - len + 1)){
res = (res + f[l][j][1][si[j] - len]) % mod;
}
}
}
}
}
g[l][r][opt][len] = res;
if(len == 0)res += (l >= 1 && r <= n);
else{
if(opt)res += (get_hash(r, 1, len) == get_hash(r, len, 1));
else res += (get_hash(l, si[l] - len + 1, si[l]) == get_hash(l, si[l], si[l] - len + 1));
}
f[l][r][opt][len] = res;
}
}
}
}
cout << f[0][n + 1][0][0] << '\n';
return 0;
}
D. bot 的殖民扩张 expansion
这个东西长的就很二分答案
\(a_1 = 1 a_n = m\) 他俩的方向确定了,然后变成了链
实际上我们需要解决的问题变成了每个点选择一个方向,覆盖所有点的最小长度
画画图能发现需要分讨,感觉好复杂,于是考场跑路了。。。。
实际上只有两种情况,设 \(f_i\) 表示考虑前 \(i\) 个点定向,能够覆盖的最大位置
-
\(f_{i - 1} >= a_i - 1\) \(i\) 向正方向
-
\(f_{i - 1} < a_i - 1\) \(i\) 向负方向,若 \(i - 2\) 能与 \(i\) 完全覆盖,可以改变 \(i - 1\) 的方向
考虑一般的情况,找到间距最大的两个点,答案一定在他们距离一半以上,且小于等于距离
考虑从这里断开环,设两点为 \(n, 1\), 有两种情况
-
\(1\) 向右,此时 \(2\) 必然向左,否则 \(1\) 左侧的点无法被覆盖
-
\(1\) 向左
然后我们直接按照链做即可
最后在 \(n\) 那里判断能不能覆盖到 \(1\) 即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1000006;
int m, n, a[maxn], f[maxn];
bool checkl(int len){
f[1] = a[1];
for(int i = 2; i <= n; ++i){
if(f[i - 1] >= a[i] - 1)f[i] = a[i] + len;
else if(f[i - 1] >= a[i] - len - 1){
if(f[i - 2] >= a[i] - len - 1)f[i] = max(a[i], a[i - 1] + len);
else f[i] = a[i];
}else return false;
}
return f[n] >= a[1] + m - 1 - len;
}
bool checkr(int len){
f[1] = a[1] + len; if(f[1] + 1 < a[2])return false; f[2] = max(f[1], a[2]);
for(int i = 3; i <= n; ++i){
if(f[i - 1] >= a[i] - 1)f[i] = a[i] + len;
else if(f[i - 1] >= a[i] - len - 1){
if(f[i - 2] >= a[i] - len - 1)f[i] = max(a[i], a[i - 1] + len);
else f[i] = a[i];
}else return false;
}
return f[n] >= min(a[1], a[2] - len) - 1 + m;
}
int main(){
freopen("expansion.in","r",stdin);
freopen("expansion.out","w",stdout);
m = read(), n = read(); if(n == 1){printf("%d\n",m - 1);return 0;}
for(int i = 1; i <= n; ++i)a[i] = read();
sort(a + 1, a + n + 1);
int mx = a[1] + m - a[n], pos = a[n];
for(int i = 1; i < n; ++i)if(mx < a[i + 1] - a[i]){
mx = a[i + 1] - a[i]; pos = a[i];
}
for(int i = 1; i <= n; ++i)a[i] = (a[i] - pos + m - 1) % m + 1;
sort(a + 1, a + n + 1);
int l = 0, r = mx - 1, ans = mx;
while(l <= r){
int mid = (l + r) >> 1;
if(checkl(mid) || checkr(mid))ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n",ans);
return 0;
}
12
A. 兄弟们
直接离线然后开桶
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
int n, m, fa[maxn][19], dep[maxn], ans[maxn];
vector<int>g[maxn];
vector<pii>q[maxn];
void dfs(int x){
for(int v : g[x]){
dep[v] = dep[x] + 1;
dfs(v);
}
}
int t[maxn];
void solve(int x){
for(pii now : q[x])ans[now.second] -= t[now.first];
++t[dep[x]];
for(int v : g[x])solve(v);
for(pii now : q[x])ans[now.second] += t[now.first];
}
int main(){
// freopen("brother.in","r",stdin);
// freopen("brother.out","w",stdout);
n = read(); for(int i = 1; i <= n; ++i)g[fa[i][0] = read()].push_back(i);
for(int v : g[0]){dep[v] = 1; dfs(v);}
for(int j = 1; j <= 18; ++j)
for(int i = 1; i <= n; ++i)
fa[i][j] = fa[fa[i][j - 1]][j - 1];
m = read();
for(int i = 1; i <= m; ++i){
int a = read(), k = read(); pii now = pii(dep[a], i);
for(int j = 0; j <= 18; ++j)if((1 << j) & k)a = fa[a][j];
if(a)q[a].push_back(now);
}
solve(0);
for(int i = 1; i <= m; ++i)printf("%d\n",max(0, ans[i] - 1));
return 0;
}
B. Vifact与猫猫
下面做法本质上是个暴力,只不过剪枝很多,通过构造数据应该能卡掉
就是 \(n^2\) 暴力,然后用线段树维护区间信息,通过上下界以及 \(DP\) 值及时(?)回溯
感觉能卡到 \(n^2log\)
说一下正解思路,不太想打
只考虑最大的限制,对于每个点 \(i\) 可以得到能更新他的最小位置 \(g_i\)
显然 \(g_i\) 单调
那么我们对最大值的限制直接转移到了序列位置上
于是考虑按照最小值的限制进行分治
取最小值限制的最大值为 \(mid\), 处理左区间对右区间的贡献
此时最小值限制都为分治点,随着考虑右区间位置的右移,可选范围也右扩,变化相同
那么当 \(len(R) < len(L)\) 的时候按照顺序考虑右区间每个位置即可
那么当 \(len(R) > len(L)\) 是如何保证复杂度的正确?
发现当处理到 \(mid + len(L)\) 时,整个左区间都满足最小值的限制,只有 \(g_i\) 还在继续限制,找到在左区间的 \(g_i\) 然后反过来用左区间更新右区间,使用线段树进行维护即可
实际打起来细节挺多。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1000005;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n, c[maxn], d[maxn];
struct note{
int f, g;
friend note operator + (const note x, const note y){
if(x.f > y.f)return x;
if(x.f < y.f)return y;
return {x.f, (x.g + y.g) % mod};
}
}f[maxn];
struct seg{
struct node{
int mxc, mic, mxd, mid; note f;
node(){mxc = mxd = -inf; mic = mid = inf; f = {-inf, 0};}
}t[maxn << 2 | 1];
void modify(int x, int l, int r, int pos){
if(l == r){
t[x].mxc = t[x].mic = c[l];
t[x].mxd = t[x].mid = d[l];
t[x].f = f[l - 1];
return;
}
int mid = (l + r) >> 1;
if(pos <= mid)modify(x << 1, l, mid, pos);
else modify(x << 1 | 1, mid + 1, r, pos);
t[x].f = t[x << 1].f + t[x << 1 | 1].f;
t[x].mxc = max(t[x << 1].mxc, t[x << 1 | 1].mxc);
t[x].mxd = max(t[x << 1].mxd, t[x << 1 | 1].mxd);
t[x].mic = min(t[x << 1].mic, t[x << 1 | 1].mic);
t[x].mid = min(t[x << 1].mid, t[x << 1 | 1].mid);
}
void query(int x, int l, int r, int pos, int liml, int limr){
if(l > pos)return;
if(r <= pos){
if(t[x].f.f < f[pos].f)return;
if(max(t[x].mic, liml) > pos - l + 1)return;
if(min(t[x].mxd, limr) < pos - r + 1)return;
if(max(t[x].mxc, liml) <= pos - r + 1 && min(t[x].mid, limr) >= pos - l + 1){
f[pos] = f[pos] + t[x].f;
return;
}
}
int mid = (l + r) >> 1, ls = x << 1, rs = x << 1 | 1;
if(t[ls].f.f > t[rs].f.f){
query(ls, l, mid, pos, max(liml, t[rs].mxc), min(limr, t[rs].mid));
query(rs, mid + 1, r, pos, liml, limr);
}else{
query(rs, mid + 1, r, pos, liml, limr);
query(ls, l, mid, pos, max(liml, t[rs].mxc), min(limr, t[rs].mid));
}
}
}T;
int main(){
freopen("cat.in","r",stdin);
freopen("cat.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)c[i] = read(), d[i] = read();
f[0].g = 1;
for(int i = 1; i <= n; ++i){
T.modify(1, 1, n, i);
f[i].f = -inf;
T.query(1, 1, n, i, -inf, inf);
++f[i].f;
}
if(f[n].f <= 0)printf("NO\n");
else printf("%d %d\n",f[n].f, f[n].g);
return 0;
}
std
#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define P 1000000007
int n, c[N], d[N], tree1[N << 2];
priority_queue<int, vector<int>, greater<int> > q, qdel;
void inc(int& x, int y) {
x += y;
if (x >= P)
x -= P;
}
struct data {
int x, y;
} tree[N << 2], lazy[N << 2], f[N];
void upd(data& a, data b) {
if (b.x > a.x)
a = b;
else if (b.x == a.x)
inc(a.y, b.y);
}
void down(int k, int L, int R) {
int mid = L + R >> 1;
upd(tree[k << 1], (data){ lazy[k].x, 1ll * (mid - L + 1) * lazy[k].y % P });
upd(tree[k << 1 | 1], (data){ lazy[k].x, 1ll * (R - mid) * lazy[k].y % P });
upd(lazy[k << 1], lazy[k]);
upd(lazy[k << 1 | 1], lazy[k]);
lazy[k] = (data){ 0, 0 };
}
data merge(data p, data q) {
if (p.x < 0 && q.x < 0)
return p;
if (p.x > q.x)
return p;
else if (p.x < q.x)
return q;
return (data){ p.x, (p.y + q.y) % P };
}
data query(int k, int l, int r, int L, int R) {
if (l > r)
return (data){ -N, 0 };
if (L == l && R == r)
return tree[k];
if (lazy[k].x)
down(k, L, R);
int mid = L + R >> 1;
if (r <= mid)
return query(k << 1, l, r, L, mid);
else if (l > mid)
return query(k << 1 | 1, l, r, mid + 1, R);
else
return merge(query(k << 1, l, mid, L, mid), query(k << 1 | 1, mid + 1, r, mid + 1, R));
}
void modify(int k, int l, int r, data p, int L, int R) {
if (l > r)
return;
if (L == l && R == r) {
if (p.x > tree[k].x)
tree[k].x = p.x, tree[k].y = 1ll * p.y * (r - l + 1) % P, lazy[k] = p;
else if (p.x == tree[k].x)
inc(tree[k].y, 1ll * p.y * (r - l + 1) % P), lazy[k].x = p.x, inc(lazy[k].y, p.y);
return;
}
if (lazy[k].x)
down(k, L, R);
int mid = L + R >> 1;
if (r <= mid)
modify(k << 1, l, r, p, L, mid);
else if (l > mid)
modify(k << 1 | 1, l, r, p, mid + 1, R);
else
modify(k << 1, l, mid, p, L, mid), modify(k << 1 | 1, mid + 1, r, p, mid + 1, R);
tree[k] = merge(tree[k << 1], tree[k << 1 | 1]);
}
int query2(int k, int l, int r, int L, int R) {
if (L == l && R == r)
return tree1[k];
int mid = L + R >> 1;
if (r <= mid)
return query2(k << 1, l, r, L, mid);
else if (l > mid)
return query2(k << 1 | 1, l, r, mid + 1, R);
else {
int x = query2(k << 1, l, mid, L, mid), y = query2(k << 1 | 1, mid + 1, r, mid + 1, R);
if (c[x] > c[y])
return x;
else
return y;
}
}
void build(int k, int l, int r) {
if (l == r) {
tree1[k] = l;
tree[k] = f[l];
return;
}
int mid = l + r >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
tree1[k] = c[tree1[k << 1]] > c[tree1[k << 1 | 1]] ? tree1[k << 1] : tree1[k << 1 | 1];
tree[k] = merge(tree[k << 1], tree[k << 1 | 1]);
}
void pre() {
int x = 0;
for (int i = 1; i <= n; i++) {
q.push(d[i]);
while (!qdel.empty() && q.top() == qdel.top()) q.pop(), qdel.pop();
while (q.top() + x < i) qdel.push(d[++x]);
tree1[i] = x;
}
while (!q.empty()) q.pop();
while (!qdel.empty()) qdel.pop();
for (int i = 1; i <= n; i++) d[i] = tree1[i];
build(1, 0, n);
}
void solve(int l, int r) {
if (l == r && l) {
data p = query(1, l, l, 0, n);
if (p.x <= f[l].x)
modify(1, l, l, f[l], 0, n);
if (p.x >= f[l].x)
upd(f[l], p);
}
if (l >= r)
return;
int k = query2(1, l + 1, r, 0, n);
solve(l, k - 1);
if (d[k] < k) {
data p = query(1, l, k - c[k] - 1, 0, n);
for (int i = max(l + c[k], k); i <= r && d[i] <= l && i <= k - 1 + c[k]; i++) {
upd(p, f[i - c[k]]);
upd(f[i], (data){ p.x + 1, p.y });
}
int L = k, R = r, t = k - 1;
while (L <= R) {
int mid = L + R >> 1;
if (d[mid] <= l)
t = mid, L = mid + 1;
else
R = mid - 1;
}
modify(1, k + c[k], t, (data){ p.x + 1, p.y }, 0, n);
for (int i = t + 1; d[i] <= k - 1 && i <= r; i++) {
p = query(1, d[i], min(i - c[k], k - 1), 0, n);
upd(f[i], (data){ p.x + 1, p.y });
}
}
solve(k, r);
}
int main() {
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> c[i] >> d[i];
f[0].x = 0, f[0].y = 1;
for (int i = 1; i <= n; i++) f[i].x = -N, f[i].y = 0;
pre();
solve(0, n);
if (f[n].x >= 0)
cout << f[n].x << ' ' << f[n].y << endl;
else
cout << "NO";
return 0;
}
C. 数论计算
题目错了。
问的是能凑出多少数,然后就是同余最短路板子
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read(){
ll x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 5e5 + 55;
const int mod = 1e9 + 7;
int n, a[105], mi = 0x3f3f3f3f;
ll l, r, dis[maxn];
queue<int>q;
bool vis[maxn];
ll sol(ll x){
ll ans = 0;
for(int i = 0; i < mi; ++i)if(x >= dis[i])ans = (ans + (x - dis[i]) / mi + 1) % mod;
return ans;
}
int main(){
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
n = read(), l = read(), r = read();
for(int i = 1; i <= n; ++i){
a[i] = read();
if(a[i] == 0){--i; --n; continue;}
mi = min(mi, a[i]);
}
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0; q.push(0);
while(!q.empty()){
int x = q.front(); q.pop(); vis[x] = false;
for(int i = 1; i <= n; ++i){
int v = (x + a[i]) % mi;
if(dis[v] > dis[x] + a[i]){
dis[v] = dis[x] + a[i];
if(!vis[v]){q.push(v); vis[v] = true;}
}
}
}
printf("%d\n",(sol(r) - sol(l - 1) + mod) % mod);
return 0;
}
D. 黑白图
首先考虑树怎么做,发现按照深度奇偶性染色,每个点初始一个该颜色的球,需要把不同颜色球进行匹配
那么考虑一条边的贡献,发现就是子树内两种颜色球的数量差
现在考虑加上一条边的效果
如果形成了奇环,那么他连接的两个同色点,有边相连他们应该是异色的,这不乱了
其实我们可以把一个球经过这条边可以看成其改变颜色
那么删去这条边,按照树做,两种颜色球的数量差为 \(cnt\)
那么我们分别把 \(cnt / 2\) 球放在两个端点,然后通过这条边进行换色匹配
具体实现可以以一个端点为根,然后从另外一个端点到根都减去\(cnt / 2\)
(相当于开始把\(cnt\) 个都放到根,然后回退 \(cnt / 2\) 个到端点,此时再统计操作次数恰好是答案,根节点的 \(cnt / 2\) 为非树边的操作次数)
如果形成偶环呢,显然非环上的部分可以直接处理,现在的问题变成了均分纸牌扩展成环
我记得初中老师让我做过这个题,当时告诉我的解法是断环为链,没错 \(n^2\)
这里显然不行了,仔细思考断环为链做法的过程,我们其实能确定下来每个权值的相对大小
我们要做的是确定一个 \(0\), 使得他们到 \(0\) 的距离和最小,那么取中位数即可
具体实现与奇环类似
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
int n, m, ans, s = 1, t;
vector<int>g[maxn];
int si[maxn], fa[maxn], st[maxn], top;
void solve(int x, int dep){
si[x] = dep;
for(int v : g[x])if(v != fa[x]){
fa[v] = x;
solve(v, -dep);
si[x] += si[v];
}
}
struct DSU{
int f[maxn];
void init(){for(int i = 1; i <= n; ++i)f[i] = i;}
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){f[fa(x)] = fa(y);}
}S;
#define nosolution() {printf("-1\n"); return 0;}
int main(){
freopen("dye.in","r",stdin);
freopen("dye.out","w",stdout);
n = read(), m = read(); S.init();
for(int i = 1; i <= m; ++i){
int u = read(), v = read();
if(S.fa(u) == S.fa(v)){
s = u; t = v;
continue;
}
g[u].push_back(v);
g[v].push_back(u);
S.merge(u, v);
}
solve(s, 1);
if(m == n - 1){
if(si[1])ans = -1;
}else{
for(int x = t; x; x = fa[x])st[++top] = si[x];
if(top & 1){
if(si[s] & 1)ans = -1;
else{
int dt = si[s] >> 1;
for(int x = t; x; x = fa[x])si[x] -= dt;
}
}else{
if(si[s])ans = -1;
else{
sort(st + 1, st + top + 1);
int dt = st[(top + 1) >> 1];
for(int x = t; x; x = fa[x])si[x] -= dt;
}
}
}
if(ans == 0){for(int i = 1; i <= n; ++i)ans += abs(si[i]);}
printf("%d\n",ans);
return 0;
}
13
A. string
多项式板子题
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 4000006;
const int mod = 998244353;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int deg, wn[maxn], rev[maxn];
void init(int len){
deg = 1; while(deg < len)deg <<= 1;
wn[0] = 1; wn[1] = qpow(3, (mod - 1) / deg);
for(int i = 1; i < deg; ++i){
rev[i] = rev[i >> 1] >> 1;
if(i & 1)rev[i] |= (deg >> 1);
wn[i] = 1ll * wn[i - 1] * wn[1] % mod;
}
}
struct poly{
int f[maxn];
int &operator [](const int &i){return f[i];}
void ntt(){
for(int i = 1; i < deg; ++i)if(i < rev[i])swap(f[i], f[rev[i]]);
for(int l = 2, hl = 1; l <= deg; l <<= 1, hl <<= 1)
for(int i = 0; i < deg; i += l)
for(int j = i; j < i + hl; ++j){
int x = f[j], y = 1ll * f[j + hl] * wn[deg / l * (j - i)] % mod;
f[j] = (x + y) % mod; f[j + hl] = (x - y + mod) % mod;
}
}
void intt(){
ntt(); reverse(f + 1, f + deg); int Inv = qpow(deg, mod - 2);
for(int i = 0; i < deg; ++i)f[i] = 1ll * f[i] * Inv % mod;
}
}f, g;
char s[maxn], t[maxn];
int ans[maxn], k, n, m;
void solve(char c){
for(int i = 0; i < deg; ++i)f[i] = s[i] == c;
for(int i = 0; i < deg; ++i)g[i] = t[i] == c;
f.ntt(); g.ntt();
for(int i = 0; i < deg; ++i)f[i] = 1ll * f[i] * g[i] % mod;
f.intt();
for(int i = 0; i < n; ++i)ans[i] += f[m - 1 + i];
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d",&k); scanf("%s%s",s, t);
n = strlen(s); m = strlen(t);
reverse(t, t + m); init(n + m);
solve('s'); solve('y'); solve('f');
solve('a'); solve('k');
solve('n'); solve('o'); solve('i');
int res = 0;
for(int i = 0; i < n - m + 1; ++i)res += (ans[i] >= m - k);
printf("%d\n",res);
return 0;
}
B. Tree
\(f_{x, i}\) 表示,\(x\) 子树内, \(x\) 所在二叉树转成链以后的最大深度为 \(i\),的最大贡献
贡献只考虑 \(x\) 子树,就是认为此时 \(dep_x = 1\)
\(g_{x} = max(f_{x, i})\)
那么要么不选,要么选左/右子树,要么都选
不选就是 \(f[x][1] = \sum g_{son} + size_x\)
选右儿子 \(f[x][i + 1] = f[son][i] + size_x\)
这种情况只在有一个儿子时考虑(为啥?)
选左儿子,可以发现一定不优
两个都选,那就
\(f[x][i + j + 1] = f[son_x][i] + f[son_y][j] + \sum g_{son}[son != son_x \&\& son != son_y ] + (size_x - size_{son_x}) * (i + 1)\)
暴力搞能过
优化的话,发现 \(son_y\) 的枚举意义不大,开数组维护 \(f[son_y][j] - g[son_y]\) 的最值即可
那么前后缀都做一次(或者维护最大次大?)即可
code
#include<bits/stdc++.h>
using namespace std;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 5005;
vector<int>g[maxn];
int si[maxn], n, f[maxn][maxn], mf[maxn];
void solve(int x){
si[x] = 1; int sum = 0;
for(int v : g[x]){solve(v); si[x] += si[v]; sum += mf[v];}
for(int i = 1; i <= si[x]; ++i)f[x][i] = -0x3f3f3f3f;
f[x][1] = sum + si[x];
if(g[x].size() == 1)
for(int v : g[x])
for(int i = 1; i <= si[v]; ++i)
f[x][i + 1] = f[v][i] + si[x];
for(int u : g[x])for(int v : g[x])if(u != v){
for(int i = 1; i <= si[u]; ++i)
for(int j = 1; j <= si[v]; ++j)
f[x][i + j + 1] = max(f[x][i + j + 1], f[u][i] + f[v][j] + sum - mf[u] - mf[v] + (si[x] - si[u]) * (i + 1));
}
for(int i = 1; i <= si[x]; ++i)mf[x] = max(mf[x], f[x][i]);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n = read();
for(int i = 2; i <= n; ++i)g[read()].push_back(i);
solve(1); printf("%d\n", mf[1]);
return 0;
}
C. sort
平衡树套 \(Trie\) 树
前三个操作,定一个操作顺序,然后就可以在线段树上搞,问题在于排序的操作
关于二进制的操作,我们尝试使用 \(0 / 1 Trie\)
发现如果初始每个位置一棵 \(Trie\),排序只需要把对应区间的 \(Trie\) 合并起来就行
而前三个操作在 \(Trie\) 树上也可以通过 \(Trie\) 树合并 / 交换左右儿子实现
那么我们使用 \(Trie\) 进行维护,而外面还有区间信息,使用平衡树进行维护
平衡树节点维护有序连续段对应的 \(Trie\) 树的根,每次操作把对应区间 \(cut\) 出来进行
\(cut\) 时候需要把 \(Trie\) 树按照大小分开,所以需要 \(Trie\) 树分裂
根据 \(ODT\) 的思想,排序时候暴力这么搞就行
那么再回去想如何维护前三个操作,发现有在平衡树上的标记,有需要下传到 \(Trie\) 的标记,那么在平衡树上就需要维护这两种标记,在需要 \(Trie\) 内数值时候再下传 \(Trie\) 的标记,均摊复杂度
而合并两个标记,按照顺序思考一下就行
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
uint read(){
uint x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1e5 + 55;
vector<uint>ans;
struct note{
uint vand, vor, vxor;
note(){vor = vand = vxor = 0;}
note(uint x, uint y, uint z){vand = x; vor = y; vxor = z;}
bool empty(){return !vor && !vand && !vxor;}
void operator += (const note &x){
vand |= x.vand; vor &= ~x.vand; vxor &= ~x.vand;
vand &= ~x.vor; vor |= x.vor; vxor &= ~x.vor;
uint v = (vand & x.vxor) | (vor & x.vxor);
vand ^= v; vor ^= v; vxor ^= v ^ x.vxor;
}
void upd(uint &x){x &= ~vand; x |= vor; x ^= vxor;}
};
struct trie{
#define lson t[x].son[0]
#define rson t[x].son[1]
struct node{
int son[2], si;
note tag;
}t[maxn * 140];
int cnt;
void insert(int &root, uint x){
if(!root)root = ++cnt;
int now = root;
for(int i = 31; i >= 0; --i){
++t[now].si;
int v = (x >> i) & 1;
if(!t[now].son[v])t[now].son[v] = ++cnt;
now = t[now].son[v];
}
++t[now].si;
}
void push_down(int x, int pos){
if(pos < 0 || t[x].tag.empty())return;
if(t[x].tag.vand >> pos & 1)lson = merge(lson, rson, pos - 1), rson = 0;
else if(t[x].tag.vor >> pos & 1)rson = merge(rson, lson, pos - 1), lson = 0;
else if(t[x].tag.vxor >> pos & 1)swap(lson, rson);
if(lson)t[lson].tag += t[x].tag;
if(rson)t[rson].tag += t[x].tag;
t[x].tag = note(0, 0, 0);
}
int merge(int x, int y, int pos){
if(!x || !y)return x | y;
push_down(x, pos); push_down(y, pos);
t[x].si += t[y].si;
lson = merge(lson, t[y].son[0], pos - 1);
rson = merge(rson, t[y].son[1], pos - 1);
return x;
}
void getans(int x, uint val, int pos, note ch){
if(pos == -1){
ch.upd(val);
while(t[x].si--)ans.push_back(val);
return;
}
push_down(x, pos);
if(lson)getans(lson, val, pos - 1, ch);
if(rson)getans(rson, val | (1u << pos), pos - 1, ch);
}
void split(int x, int &y, int k, int pos){
y = ++cnt;
t[y].si = t[x].si - k;
t[x].si = k;
if(pos == -1)return;
push_down(x, pos);
int si = t[lson].si;
if(k > si)split(rson, t[y].son[1], k - si, pos - 1);
else swap(rson, t[y].son[1]);
if(k < si)split(lson, t[y].son[0], k, pos - 1);
}
#undef lson
#undef rson
}Trie;
struct FHQ_Treap{
#define lson t[x].ls
#define rson t[x].rs
struct node{
int ls, rs, l, r, key, si, rt;
note tag1, tag2;
int len() {return r - l + 1;}
}t[maxn * 32];
int cnt, root;
int New(int root, int l, int r){
t[++cnt] = {0, 0, l, r, rand(), r - l + 1, root};
return cnt;
}
void push_up(int x){t[x].si = t[lson].si + t[rson].si + t[x].len();}
void push_down(int x){
if(t[x].tag1.empty())return;
if(lson)t[lson].tag1 += t[x].tag1, t[lson].tag2 += t[x].tag1;
if(rson)t[rson].tag1 += t[x].tag1, t[rson].tag2 += t[x].tag1;
t[x].tag1 = note(0, 0, 0);
}
int merge(int x, int y){
if(!x || !y)return x | y;
push_down(x); push_down(y);
if(t[x].key < t[y].key){
rson = merge(rson, y);
push_up(x);
return x;
}else{
t[y].ls = merge(x, t[y].ls);
push_up(y);
return y;
}
}
void split(int x, int &l, int &r, int k){
if(!x)return l = r = 0, void();
push_down(x);
if(t[lson].si >= k){split(lson, l, lson, k); r = x;}
else{split(rson, rson, r, k - t[lson].si - t[x].len()); l = x;}
push_up(x); return;
}
vector<int>tmp;
void cut(int k){
int l = 0, r = 0;
split(root, l, r, k);
if(t[l].si == k){
root = merge(l, r);
return;
}
int len = t[l].si - k;
tmp.resize(0);
int x = l;
tmp.push_back(x);
while(rson)x = rson, tmp.push_back(x);
int lrt = 0;
Trie.split(t[x].rt, lrt, t[x].len() - len, 31);
int now = New(lrt, t[x].r - len + 1, t[x].r);
t[now].tag2 = t[x].tag2;
t[x].r -= len;
for(int i = tmp.size() - 1; i >= 0; --i)push_up(tmp[i]);
root = merge(merge(l, now), r);
}
void upd(int L, int R, note opt){
cut(L - 1); cut(R);
int l = 0, m = 0, r = 0;
split(root, l, r, R);
split(l, l, m, L - 1);
t[m].tag1 += opt;
t[m].tag2 += opt;
root = merge(merge(l, m), r);
}
int nrt;
void dfs(int &x){
if(!x)return;
push_down(x);
Trie.t[t[x].rt].tag += t[x].tag2;
t[x].tag2 = note(0, 0, 0);
nrt = Trie.merge(nrt, t[x].rt, 31);
t[x].rt = 0;
dfs(lson); dfs(rson); x = 0;
}
void sort(int l, int r){
cut(l - 1); cut(r);
int L = 0, M = 0, R = 0;
split(root, L, R, r);
split(L, L, M, l - 1);
nrt = 0; dfs(M);
root = merge(merge(L, New(nrt, l, r)), R);
}
void getans(int x){
push_down(x);
if(lson)getans(lson);
Trie.getans(t[x].rt, 0, 31, t[x].tag2);
if(rson)getans(rson);
}
#undef lson
#undef rson
}T;
int n, m;
int main(){
// freopen("sort.in","r",stdin);
// freopen("sort.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= n; ++i){
int rt = 0, x = read();
Trie.insert(rt, x);
T.root = T.merge(T.root, T.New(rt, i, i));
}
for(int i = 1; i <= m; ++i){
int opt = read(), l = read(), r = read(); uint w;
if(opt != 4)w = read();
if(opt == 1)T.upd(l, r, note(0, w, 0));
if(opt == 2)T.upd(l, r, note(~w, 0, 0));
if(opt == 3)T.upd(l, r, note(0, 0, w));
if(opt == 4)T.sort(l, r);
}
T.getans(T.root);
for(uint x : ans)printf("%u ",x);printf("\n");
return 0;
}
14
A. 钩子
然而还是不会做,不过这次理解的更深了。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1025;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
int n, mod;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
bool vis[maxn];
int f[maxn][maxn], pos[maxn], tmp[maxn], cnt[maxn], c0[maxn], inv[maxn];
int ans[maxn][maxn];
int main(){
// freopen("gou.in","r",stdin);
// freopen("gou.out","w",stdout);
n = read(), mod = read();
vis[0] = vis[n + 1] = true;
for(int i = 1; i <= n; ++i){
int L = 0, R = -1;
for(int l = 1, r; l <= n; l = r + 1){
r = l; if(vis[r])continue;
while(!vis[r + 1]) ++r;
if(r - l > R - L)R = r, L = l;
}
int dis = (R - L) >> 1;
++cnt[dis]; c0[dis] += ((R - L) & 1);
pos[i] = L + dis; vis[pos[i]] = true;
}
for(int i = 1; i <= n; ++i)inv[i] = qpow(i, mod - 2);
int sum = n;
for(int i = 0; i <= n; ++i)if(cnt[i]){
int l = sum - cnt[i] + 1, r = sum;
if(i == 0){
for(int j = l; j <= r; ++j)
for(int k = l; k <= r; ++k)
ans[j][pos[k]] = inv[cnt[i]];
sum -= cnt[0];
continue;
}
for(int j = 0; j <= cnt[i]; ++j)
for(int k = 0; k <= c0[i]; ++k)
f[j][k] = 0;
f[0][c0[i]] = 1;
for(int j = 0; j < cnt[i]; ++j){
int p0 = 0, p1 = 0;
for(int k = 0; k <= c0[i]; ++k)if(f[j][k]){
int res = cnt[i] - j + k;
if(k){
int w = 2ll * f[j][k] * k % mod * inv[res] % mod;
p0 = (p0 + 1ll * w * inv[c0[i]] % mod * inv[2]) % mod;
f[j + 1][k - 1] = (f[j + 1][k - 1] + w) % mod;
}
if(cnt[i] - j - k > 0){
int w = 1ll * f[j][k] * (res - k - k) % mod * inv[res] % mod;
p1 = (p1 + 1ll * w * inv[cnt[i] - c0[i]]) % mod;
f[j + 1][k] = (f[j + 1][k] + w) % mod;
}
}
int k = l + j;
for(int p = l; p <= l + c0[i] - 1; ++p)
ans[k][pos[p]] = (ans[k][pos[p]] + p0) % mod, ans[k][pos[p] + 1] = (ans[k][pos[p] + 1] + p0) % mod;
for(int p = l + c0[i]; p <= r; ++p)
ans[k][pos[p]] = (ans[k][pos[p]] + p1) % mod;
}
for(int x = r + 1; x <= n; ++x)
for(int p = l; p <= l + c0[i] - 1; ++p){
int L = pos[p] - i, R = pos[p] + i + 1;
for(int k = L; k <= R; ++k)if(k != pos[p])tmp[k] = ans[x][k];
for(int k = L; k <= R; ++k)ans[x][k] = 1ll * inv[2] * (tmp[k] + tmp[pos[p] + pos[p] + 1 - k]) % mod;
}
sum -= cnt[i];
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j)printf("%d ",ans[i][j]);
printf("\n");
}
return 0;
}
B. 加减
可以证明 \(k\) 的答案为 \(k + 2\) 的答案删去两个,于是可以用线段树维护一堆东西来搞,下面讲另外一种做法
设 \(f_{x}\) 表示选择长度为 \(x\) 的子序列的最大权值
直接转移 \(n^2\) 考虑如何优化
如果使用分治,瓶颈在于合并两个区间信息(\(max\) 卷积?),考虑观察 \(f\) 的性质
发现 \(f\) 的奇数位和偶数位分别为一个凸包,于是可以分别维护这两个东西
合并凸包时候可以先进行差分,把他们变成向量,然后归并排序即可
维护正负开头的奇偶一共四个凸包
下面是我的神奇理解方式
差分出来看成多选了一组,然后拼接就更好考虑了
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; bool f = false; char c = getchar();
while(!isdigit(c)){f = c == '-'; c = getchar();}
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int maxn = 500005;
const ll inf = 1e18;
int n, a[maxn];
vector<ll>f[maxn << 2 | 1][4];
vector<ll> merge(const vector<ll> &x, const vector<ll> &y, bool add0){
vector<ll>ans; if(add0)ans.push_back(0);
ans.push_back(x[0] + y[0]);
int px = 1, py = 1;
while(px < x.size() && py < y.size()){
if(x[px] > y[py]){ans.push_back(x[px]); ++px;}
else {ans.push_back(y[py]); ++py;}
}
while(px < x.size()){ans.push_back(x[px]); ++px;}
while(py < y.size()){ans.push_back(y[py]); ++py;}
return ans;
}
void sum(vector<ll>&x){for(int i = 1; i < x.size(); ++i)x[i] += x[i - 1];}
void Max(vector<ll>x, vector<ll>y, vector<ll>&ans){
sum(x); sum(y);
for(int i = 0; i < min(x.size(), y.size()); ++i)ans.push_back(max(x[i], y[i]));
for(int i = x.size(); i < y.size(); ++i)ans.push_back(y[i]);
for(int i = y.size(); i < x.size(); ++i)ans.push_back(x[i]);
for(int i = ans.size() - 1; i >= 1; --i)ans[i] = ans[i] - ans[i - 1];
}
void solve(int x, int l, int r){
if(l == r){
f[x][0].push_back(a[l]);
f[x][1].push_back(0);
f[x][2].push_back(-a[l]);
f[x][3].push_back(0);
return;
}
int mid = (l + r) >> 1;
solve(x << 1, l, mid);
solve(x << 1 | 1, mid + 1, r);
Max(merge(f[x << 1][0], f[x << 1 | 1][3], 0), merge(f[x << 1][1], f[x << 1 | 1][0], 0), f[x][0]);
Max(merge(f[x << 1][0], f[x << 1 | 1][2], 1), merge(f[x << 1][1], f[x << 1 | 1][1], 0), f[x][1]);
Max(merge(f[x << 1][2], f[x << 1 | 1][1], 0), merge(f[x << 1][3], f[x << 1 | 1][2], 0), f[x][2]);
Max(merge(f[x << 1][2], f[x << 1 | 1][0], 1), merge(f[x << 1][3], f[x << 1 | 1][3], 0), f[x][3]);
}
int main(){
freopen("jia.in","r",stdin);
freopen("jia.out","w",stdout);
n = read(); for(int i = 1; i <= n; ++i)a[i] = read();
solve(1, 1, n);
sum(f[1][0]); sum(f[1][1]);
for(int i = 1; i <= n; ++i)
printf("%lld ",f[1][!(i & 1)][i >> 1]); printf("\n");
return 0;
}
C. 树高
P5529 [Ynoi2012] 梦断 SCOI2017
口胡一下,不一定写
用 \(ETT\) 维护同色连通块
对于与父亲异色的儿子们,每种颜色合并起来(括号序并列),记录一个代表元
这样每次操作影响的联通块数就是\(O(1)\) 的了
然后 \(1\) 操作考虑其与父亲的关系进行操作
\(3\) 操作直接维护子树信息即可
对于 \(2\) 操作,在平衡树上维护该区间内的点的异色儿子是否存在颜色 \(x\)
总的 \(1\) 操作影响有限,势能分析一下每次暴力遍历就是正确的
大概?
code
15
A. ⼩W数排列
连续段 \(DP\) 贡献提前处理
把 \(a\) 排序,按照从大到小的顺序插入,每次考虑是新建段,接在某个段一侧还是合并两个段
这样就能提前计算填一个数的贡献了
特别的两端的点贡献不同,于是新加两维 \(1 / 0\) 表示左右边界
然后就是分讨
但是直接搞会 \(T\) 飞
但是加上一个剪枝就行了
if(it.first - a[i + 1] * (j + j - p - q) > l)continue;
仔细思考一下,发现这个东西保证了复杂度,他保证了任意时刻最后一维总状态数最多为 \(l\)
如果每次把 \('0'\) 看成当前要加入的数,那么最后一维就是确定的 \(l\),可以变成静态的
而且处理边界的时候没有区分左右边界的需要,改成 \(0 / 1 / 2\) 节省一部分讨论和状态
code
// ubsan: undefined
// accoders
#pragma GCC optimize(3)
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 105;
const int mod = 1e9 + 7;
int n, l, a[maxn];
unordered_map<int, int>f[maxn][2][2], tmp[maxn][2][2];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
int calc(int x){return x * (x - 1);}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
n = read(); l = read();
if(n == 1){
printf("%d\n",1);
return 0;
}
for(int i = 1; i <= n; ++i)a[i] = read();
sort(a + 1, a + n + 1); reverse(a + 1, a + n + 1);
tmp[0][0][0][0] = 1;
for(int i = 0; i <= n; ++i){
int up = min(i, n - i + 1);
for(int j = 0; j <= up; ++j)
for(int p = 0; p <= 1; ++p)
for(int q = 0; q <= 1; ++q)
swap(tmp[j][p][q], f[j][p][q]), tmp[j][p][q].clear();
if(i == n)break;
for(int j = 0; j <= up; ++j)
for(int p = 0; p <= 1; ++p)
for(int q = 0; q <= 1; ++q)
for(auto it : f[j][p][q]){
if(!it.second || it.first - a[i + 1] * (j + j - p - q) > l)continue;
add(tmp[j + 1][p][q][it.first + a[i + 1] + a[i + 1]], it.second);
if(!p)add(tmp[j + 1][1][q][it.first + a[i + 1]], it.second);
if(!q)add(tmp[j + 1][p][1][it.first + a[i + 1]], it.second);
if(j){
add(tmp[j][p][q][it.first], (2ll * j - p - q) * it.second % mod);
if(!p && j > q)add(tmp[j][1][q][it.first - a[i + 1]], 1ll * (j - q) * it.second % mod);
if(!q && j > p)add(tmp[j][p][1][it.first - a[i + 1]], 1ll * (j - p) * it.second % mod);
if(i == n - 1 && j == 1 && (p ^ q))add(tmp[j][1][1][it.first - a[i + 1]], it.second);
if(j > 1){
if(j > p + q)add(tmp[j - 1][p][q][it.first - a[i + 1] - a[i + 1]], 1ll * calc(j - p - q) * it.second % mod);
if(p && j > q + 1)add(tmp[j - 1][p][q][it.first - a[i + 1] - a[i + 1]], 1ll * (j - q - 1) * it.second % mod);
if(q && j > p + 1)add(tmp[j - 1][p][q][it.first - a[i + 1] - a[i + 1]], 1ll * (j - p - 1) * it.second % mod);
if(i == n - 1 && j == 2 && p && q)add(tmp[j - 1][1][1][it.first - a[i + 1] - a[i + 1]], it.second);
}
}
}
}
int ans = 0;
for(auto it : f[1][1][1])if(it.first <= l)add(ans, it.second);
printf("%d\n",ans);
return 0;
}
⼩W玩游戏
麻了,生成函数
\(e^x = {1, 1, 1, 1, 1 .....}\)
\(e^{-x} = {1, -1, 1, -1, 1 .....}\)
显然每次操作改变一行一列的奇偶性,行列无关
设 \(f_i\) 表示 \(i\) 行/列为奇数的方案数
那么
\(f_i = \binom{n}{i}q![x^q](\frac{e^x - e^{-x}}{2})^i(\frac{e^x + e^{-x}}{2})^{n - i}\)
然后就是推式子
直接粘 joke大佬的题解
用到结论 \((e^x)^c = {c^0, c^1, c^2....}\)
以及两次?二项式定理
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read(){
ll x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 800005;
const int mod = 998244353;
ll n, m, q, k;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int deg, wn[maxn], rev[maxn];
void init(int len){
deg = 1; while(deg < len)deg <<= 1;
wn[0] = 1; wn[1] = qpow(3, (mod - 1) / deg);
for(int i = 1; i < deg; ++i){
rev[i] = (rev[i >> 1] >> 1);
if(i & 1)rev[i] |= (deg >> 1);
wn[i] = 1ll * wn[i - 1] * wn[1] % mod;
}
}
struct poly{
int f[maxn];
int &operator [](const int &i){return f[i];}
void ntt(){
for(int i = 1; i < deg; ++i)if(i < rev[i])swap(f[i], f[rev[i]]);
for(int l = 2, hl = 1; l <= deg; l <<= 1, hl <<= 1)
for(int i = 0; i < deg; i += l)
for(int j = i; j < i + hl; ++j){
int x = f[j], y = 1ll * wn[deg / l * (j - i)] * f[j + hl] % mod;
f[j] = (x + y) % mod; f[j + hl] = (x - y + mod) % mod;
}
}
void intt(){
ntt(); reverse(f + 1, f + deg); int Inv = qpow(deg, mod - 2);
for(int i = 0; i < deg; ++i)f[i] = 1ll * f[i] * Inv % mod;
}
void reve(int n){reverse(f, f + n + 1);}
}f, g, h;
int fac[maxn], ifac[maxn];
int c(int n, int m){return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;}
void calc(poly &f, int n){
for(int i = 0; i <= n; ++i)f[i] = 1ll * qpow((i + i - n + mod) % mod, q) * ifac[i] % mod;
for(int i = 0; i <= n; ++i)h[i] = ifac[i];
init(n + n + 2); for(int i = n + 1; i < deg; ++i)h[i] = 0;
f.ntt(); h.ntt(); for(int i = 0; i < deg; ++i)f[i] = 1ll * f[i] * h[i] % mod;
f.intt(); for(int i = n + 1; i < deg; ++i)f[i] = 0;
for(int i = 1; i <= n; ++i)f[i] = 1ll * f[i] * fac[i] % mod;
f.reve(n);
for(int i = 0; i <= n; ++i)f[i] = 1ll * f[i] * qpow(mod - 2, i) % mod * ifac[i] % mod;
f.ntt(); for(int i = 0; i < deg; ++i)f[i] = 1ll * f[i] * h[i] % mod;
f.intt(); for(int i = n + 1; i < deg; ++i)f[i] = 0;
int p = qpow((mod + 1) >> 1, n);
for(int i = 0; i <= n; ++i)f[i] = 1ll * f[i] * fac[i] % mod * c(n, i) % mod * p % mod;
}
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
n = read(), m = read(), q = read(), k = read(); q %= (mod - 1);
int mx = max(n, m) + 10; fac[0] = ifac[0] = 1;
for(int i = 1; i <= mx; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[mx] = qpow(fac[mx], mod - 2);
for(int i = mx - 1; i >= 1; --i)ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
calc(f, n); calc(g, m);
for(int i = 1; i <= m; ++i)g[i] = (g[i] + g[i - 1]) % mod;
int ans = 0;
for(int i = 0; i <= n; ++i){
if (n > 2 * i) {
if (k >= 1ll * i * m) ans = (ans + 1ll * f[i] * g[min(1ll * m, 1ll * (k - 1ll * i * m) / (n - 2ll * i))]) % mod;
} else if (n < 2 * i) {
ll l = (1ll * i * m - k + 2 * i - n - 1) / (2 * i - n);
if (l <= m) ans = (ans + 1ll * f[i] * (g[m] - (l <= 0 ? 0 : g[l - 1]) + mod)) % mod;
} else if (k >= 1ll * i * m) ans = (ans + 1ll * f[i] * g[m]) % mod;
}
printf("%d\n",ans);
return 0;
}
⼩W维护序列
码农题++
好像值域比较小时候 \(FHQ\) 的常数比动态开点线段树大的多
操作一显然可以转化成 \((s_1^3 - 3s_2s_1 + 2s_3)/ 6\),~~ 这不是容斥原理吗~~
操作五就是 \(s_0\)
\(s_i = \sum a_j^i\)
不考虑插入删除,那么去重就是二维偏序问题
对每个数维护一个 \(pre\) 记录前面最后一个与他相同的数的位置
查询区间 \([l, r]\) 就是查询 \(pre < l \&\& l <= i <= r\)
于是使用树套树进行处理
考虑多了插入删除怎么搞
把操作离线下来,先模拟一遍,但是删除只去掉他对 \(size\) 的贡献而不真的删去
这样我们得到了一个序列,满足任意时刻的序列都是他的子序列
然后维护一下 \(pre\), 树套树就能做了
建议线段树套线段树,好写好调(不用调)
使用 \(FHQ\) 的惨痛教训也在代码里,您可以尝试用他跑一跑
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
int sta[20];
void print(int x){
int top = 0;
while(x)sta[++top] = x % 10, x /= 10;
if(!top)sta[++top] = 0;
for(int i = top; i >= 1; --i)putchar('0' + sta[i]);
putchar('\n');
}
mt19937 rd((ull)(new char) * (ull)(new char));
int sd(){return uniform_int_distribution<>(INT_MIN, INT_MAX)(rd);}
const int maxn = 200005;
const int mod = 1e9 + 7, inv6 = 166666668;
struct note{
int sum[4];
int &operator [](const int &i){return sum[i];}
int operator [](const int &i)const{return sum[i];}
friend note operator +(const note &x, const note &y){
note ans;
for(int i = 0; i < 4; ++i)
ans.sum[i] = (x.sum[i] + y.sum[i]) % mod;
return ans;
}
void init(int val){
sum[0] = 1;
for(int i = 1; i < 4; ++i)sum[i] = 1ll * sum[i - 1] * val % mod;
}
void clear(){for(int i = 0; i < 4; ++i)sum[i] = 0;}
};
int calc(const note &x){
int ans = 0;
ans = (1ll * x[1] * x[1] % mod * x[1] % mod - 3ll * x[2] * x[1] % mod + 2ll * x[3] % mod) % mod;
ans = (1ll * ans * inv6 % mod + mod) % mod;
return ans;
}
struct op{int opt, x, y, nodeid;}d[maxn];
int n, q, a[maxn], ni[maxn];
int sq[maxn], tail, nsq[maxn];
struct FHQ_Treap_seq{
#define lson t[x].l
#define rson t[x].r
struct node{
int l, r, val, key, si;
}t[maxn + maxn];
int cnt = 0, root;
int New(){t[++cnt].key = sd(); t[cnt].val = t[cnt].si = 1; return cnt;}
void push_up(int x){t[x].si = t[lson].si + t[rson].si + t[x].val;}
void split(int x, int &l, int &r, int k){
if(!x)return l = 0, r = 0, void();
if(t[lson].si >= k){split(lson, l, lson, k); r = x;}
else{split(rson, rson, r, k - t[lson].si - t[x].val); l = x;}
push_up(x);
}
int merge(int x, int y){
if(!x || !y)return x | y;
if(t[x].key < t[y].key){t[x].r = merge(t[x].r, y); push_up(x); return x;}
else {t[y].l = merge(x, t[y].l); push_up(y); return y;}
}
int kth(int k){
int l = 0, r = 0;
split(root, l, r, k);
int x = l;
while(rson)x = rson;
root = merge(l, r);
return x;
}
vector<int>st;
int erase(int k){
int l = 0, r = 0;
split(root, l, r, k);
int x = l;
while(rson)st.push_back(x), x = rson;
st.push_back(x);
t[x].val = 0;
while(st.size())push_up(st.back()), st.pop_back();
root = merge(l, r);
return x;
}
int insert(int k){
int l = 0, r = 0, ans = New();
split(root, l, r, k);
root = merge(merge(l, ans), r);
return ans;
}
void dfs(int x){
if(lson)dfs(lson);
sq[++tail] = x;
nsq[x] = tail;
if(rson)dfs(rson);
}
#undef lson
#undef rson
}pre_T;
struct FHQ_Treap_val{
#define lson t[x].l
#define rson t[x].r
struct node{
int l, r, key, pre;
note sum, val;
}t[maxn * 30];
int cnt = 0;
int New(int pre, int val){
t[++cnt].key = sd();
t[cnt].val.init(val);
t[cnt].pre = pre;
t[cnt].sum = t[cnt].val;
return cnt;
}
void push_up(int x){t[x].sum = t[lson].sum + t[rson].sum + t[x].val;}
int merge(int x, int y){
if(!x || !y)return x | y;
if(t[x].key < t[y].key){t[x].r = merge(t[x].r, y); push_up(x); return x;}
else {t[y].l = merge(x, t[y].l); push_up(y); return y;}
}
void split(int x, int &l, int &r, int kp, int kv){
if(!x)return l = 0, r = 0, void();
if(t[x].pre > kp || (t[x].pre == kp && t[x].val[1] > kv)){split(lson, l, lson, kp, kv); r = x;}
else{split(rson, rson, r, kp, kv); l = x;}
push_up(x);
}
void insert(int &rt, int pre, int val){
int l = 0, r = 0;
split(rt, l, r, pre, val);
rt = merge(merge(l, New(pre, val)), r);
}
void erase(int &rt, int pre, int val){
int l = 0, m = 0, r = 0;
split(rt, l, r, pre, val);
split(l, l, m, pre, val - 1);
rt = merge(l, r);
}
note query(int &rt, int k){
int l = 0, r = 0;
split(rt, l, r, k, INT_MAX);
note ans = t[l].sum;
rt = merge(l, r);
return ans;
}
#undef lson
#undef rson
}T;
int nowpre[maxn], nowval[maxn];
struct seg{
int rt[maxn << 2 | 1];
void insert(int x, int l, int r, int pos, int opt){
if(opt == 1)T.insert(rt[x], nowpre[pos], nowval[pos]);
else T.erase(rt[x], nowpre[pos], nowval[pos]);
if(l == r)return;
int mid = (l + r) >> 1;
if(pos <= mid)insert(x << 1, l, mid, pos, opt);
else insert(x << 1 | 1, mid + 1, r, pos, opt);
}
note query(int x, int l, int r, int L, int R){
if(L <= l && r <= R)return T.query(rt[x], L - 1);
int mid = (l + r) >> 1;
if(L <= mid && R > mid)return query(x << 1, l, mid, L, R) + query(x << 1 | 1, mid + 1, r, L, R);
if(L <= mid)return query(x << 1, l, mid, L, R);
return query(x << 1 | 1, mid + 1, r, L, R);
}
}segT;
unordered_map<int, int>mp;
set<int>s[maxn];
int snt;
#define id(val) (mp[val] ? mp[val] : mp[val] = ++snt)
void erase(int pos){
int val = nowval[pos];
int now = id(val);
segT.insert(1, 1, tail, pos, -1);
auto it = s[now].upper_bound(pos);
if(it != s[now].end()){
int nxt = *it;
segT.insert(1, 1, tail, nxt, -1);
nowpre[nxt] = nowpre[pos];
segT.insert(1, 1, tail, nxt, 1);
}
nowpre[pos] = nowval[pos] = 0;
s[now].erase(pos);
}
void insert(int pos, int val){
int now = id(val);
auto it = s[now].lower_bound(pos);
if(it != s[now].end()){
int nxt = *it;
segT.insert(1, 1, tail, nxt, -1);
nowpre[nxt] = pos;
segT.insert(1, 1, tail, nxt, 1);
}
int pre = 0;
if(it != s[now].begin())pre = *--it;
nowpre[pos] = pre; nowval[pos] = val;
segT.insert(1, 1, tail, pos, 1);
s[now].insert(pos);
}
void repalce(int pos, int val){if(val == nowval[pos])return;erase(pos); insert(pos, val);}
void query(int l, int r, int opt){
note ans = segT.query(1, 1, tail, l, r);
if(opt == 1)print(calc(ans));
else print(ans[0]);
}
void work(){
for(int i = 1; i <= n; ++i){
ni[i] = pre_T.New();
pre_T.root = pre_T.merge(pre_T.root, ni[i]);
}
for(int i = 1; i <= q; ++i){
if(d[i].opt == 1 || d[i].opt == 5){
d[i].x = pre_T.kth(d[i].x);
d[i].y = pre_T.kth(d[i].y);
}
if(d[i].opt == 2)d[i].x = pre_T.kth(d[i].x);
if(d[i].opt == 3)d[i].x = pre_T.erase(d[i].x);
if(d[i].opt == 4)d[i].x = pre_T.insert(d[i].x);
}
pre_T.dfs(pre_T.root);
for(int i = 1; i <= n; ++i)insert(nsq[ni[i]], a[i]);
cerr << 1.0 * clock() / CLOCKS_PER_SEC << endl;
for(int i = 1; i <= q; ++i){
if(d[i].opt == 1 || d[i].opt == 5)query(nsq[d[i].x], nsq[d[i].y], d[i].opt);
if(d[i].opt == 1 || d[i].opt == 5)continue;
if(d[i].opt == 2)repalce(nsq[d[i].x], d[i].y);
if(d[i].opt == 3)erase(nsq[d[i].x]);
if(d[i].opt == 4)insert(nsq[d[i].x], d[i].y);
}
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n = read(), q = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= q; ++i){
d[i].opt = read(), d[i].x = read();
if(d[i].opt != 3)d[i].y = read();
}
work();
return 0;
}
17
A. 连接(connect)
不在边界上的一定能连,绕开阻碍他们的线即可(没有说连线必须过整点)
存在不合法的情况则在边界上存在两对点连线有交
这个连线是贴着边框的
换句话说就是顺/逆时针在矩形上转,第一个点加入,第二个点弹出,需要满足栈结构
code
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cassert>
#include<cstring>
#include<complex>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1e6 + 66;
bool flag;
pii a[maxn];
int cnt, top, st[maxn], r, c, n;
int id(){
int x = read(), y = read();
if(x == 0 || y == c)return x + y;
else if(x == r || y == 0)return (r + c) * 2 - x - y;
return -1;
}
int main(){
freopen("connect.in","r",stdin);
freopen("connect.out","w",stdout);
r = read(), c = read(), n = read();
for(int i = 1; i <= n; ++i){
int x = id(), y = id();
if(x > y) swap(x, y);
if(x >= 0)a[++cnt] = {x, y};
}
sort(a + 1, a + cnt + 1);
for(int i = 1; i <= cnt; ++i){
while(top && st[top] < a[i].first)--top;
if(top && st[top] < a[i].second){
printf("NO\n");
return 0;
}
st[++top] = a[i].second;
}
printf("YES\n");
return 0;
}
B. 前行(liveon)
\(\lfloor\frac{ \lfloor \frac{x}{a} \rfloor }{b}\rfloor = \lfloor \frac{x}{ab}\rfloor\)
树剖即可
code
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cassert>
#include<cstring>
#include<complex>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read(){
ll x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1000005;
int n, m;
struct edge{
int to, net;
ll val;
}e[maxn << 1 | 1];
int head[maxn], tot;
void add(int u, int v, ll w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
assert(w);
e[tot].val = w;
}
int mp[maxn], dep[maxn], si[maxn], son[maxn], fa[maxn], top[maxn], dfn[maxn], tim, id[maxn];
ll up[maxn];
void dfs1(int x){
si[x] = 1;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa[x])continue;
fa[v] = x; dep[v] = dep[x] + 1;
up[v] = e[i].val;
mp[(i + 1) >> 1] = v;
dfs1(v);
si[x] += si[v];
if(si[son[x]] < si[v])son[x] = v;
}
}
void dfs2(int x, int tp){
dfn[x] = ++tim; id[tim] = x; top[x] = tp;
if(son[x])dfs2(son[x], tp);
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v != fa[x] && v != son[x])dfs2(v, v);
}
}
struct seg{
ll t[maxn << 2 | 1];
void push_up(int x){
if(1.1e18 / t[x << 1] < t[x << 1 | 1])
t[x] = 1.1e18;
else
t[x] = t[x << 1] * t[x << 1 | 1];
if(t[x] <= 0)t[x] = 1.1e18;
}
void build(int x, int l, int r){
if(l == r){t[x] = up[id[l]]; assert(t[x]);return;}
int mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
push_up(x);
}
void modify(int x, int l, int r, int pos){
if(l == r){t[x] = up[id[l]]; assert(t[x]); return;}
int mid = (l + r) >> 1;
if(pos <= mid)modify(x << 1, l, mid, pos);
else modify(x << 1 | 1, mid + 1, r, pos);
push_up(x);
}
ll query(int x, int l, int r, int L, int R){
if(L > R)return 1;
if(L <= l && r <= R)return t[x];
int mid = (l + r) >> 1;
ll al = 1, ar = 1;
if(L <= mid)al = query(x << 1, l, mid, L, R);
if(R > mid)ar = query(x << 1 | 1, mid + 1, r, L, R);
if(1.1e18 / al < ar)al = 1.1e18; else al *= ar;
return al;
}
}T;
void solve(int u, int v){
ll z = read(), qq;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]])swap(u, v);
qq = T.query(1, 1, n, dfn[top[u]], dfn[u]);
if(qq)z /= qq;
u = fa[top[u]];
}
if(dep[u] > dep[v])swap(u, v);
if(dep[u] != dep[v]){
qq = T.query(1, 1, n, dfn[u] + 1, dfn[v]);
z /= qq;
}
printf("%lld\n",z);
}
signed main(){
freopen("liveon.in","r",stdin);
freopen("liveon.out","w",stdout);
n = read(), m = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
ll w = read();
add(u, v, w); add(v, u, w);
}
up[1] = 1; dfs1(1);
dfs2(1, 1);
T.build(1, 1, n);
for(int i = 1; i <= m; ++i){
ll op = read(), x = read(), y = read();
if(op & 1)solve(x, y);
else {
up[mp[x]] = y;
T.modify(1, 1, n, dfn[mp[x]]);
}
}
return 0;
}
C. 飞花(flower)
发现一组合法解,只需要满足对任意 \(i, j\) 不存在 \(a_i < a_j\) 且 \(b_i < b_j\) 的情况
那么把每个卡片翻或者不翻看成两个点,那么问题就类似于 \(2-set\) 问题
然而我不会 \(2-set\), 不过这个题比较特殊
简单思考发现可以直接扩展域并查集
那么现在的问题就是边数太多
考虑对于已经确定的每个集合,如果只选择一个代表点出来,每次加入一个点,按照某种顺序考虑代表点,能够连边则合并,不能连直接跳过,那么复杂度就正确了,那么按照什么顺序呢
首先按照 \(min(a_i, b_i)\) 从小到大排序,然后按照 \(max(a_i, b_i)\) 对代表点排序,代表点选择 \(max(a_i, b_i)\) 最小的那个即可
考虑原因
对于若干点,他们的 \(min\) 都不大于当前点的 \(min\)
如果他们的 \(max\) 小于当前点 \(min\) 那么无解
如果他们的 \(max\) 不小于当前点 \(min\) 且不大于当前点 \(max\), 那么他和当前点存在限制关系(即连边)
如果他们的 $max 大于当前点 \(max\), 那么他和当前点不存在限制关系
发现 \(max\) 越小,与后面的点更有可能存在限制关系
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 5e5;
int n, val[maxn];
pii a[maxn];
struct dsu{
int f[maxn];
void init(int x){for(int i = 1; i <= x; ++i)f[i] = i;};
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){x = fa(x); y = fa(y); f[y] = x;}
}S;
bool cmp(const pii &x, const pii &y){return min(x.first, x.second) < min(y.first, y.second);}
priority_queue<pii, vector<pii>, greater<pii>>q;
int main(){
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)a[i].first = read(), a[i].second = read();
S.init(n + n); sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; ++i){
int p = i;
while(!q.empty() && q.top().first < max(a[i].first, a[i].second)){
if(q.top().first < min(a[i].first, a[i].second)){
printf("-1\n"); return 0;
}
int x = q.top().second; q.pop();
if((a[x].first < a[i].first) == (a[x].second < a[i].second)){
S.merge(x, i + n); S.merge(x + n, i);
}else{
S.merge(x, i); S.merge(x + n, i + n);
}
if(max(a[x].first, a[x].second) < max(a[p].first, a[p].second))p = x;
}
q.push(pii(max(a[p].first, a[p].second), p));
}
for(int i = 1; i <= n; ++i)if(S.fa(i) == S.fa(i + n)){
printf("-1\n"); return 0;
}
for(int i = 1; i <= n; ++i)++val[S.fa(i)];
int ans = 0;
for(int i = 1; i <= n; ++i)ans += min(val[S.fa(i)], val[S.fa(i + n)]), val[S.fa(i)] = val[S.fa(i + n)] = 0;
printf("%d\n",ans);
return 0;
}
D. 幻想(fantasy)
发现前 \(i\) 个数能够凑出的可能合法的数一定能表示成 \(m \% a_{i + 1} + xa_{i + 1}\) 的形式
设 \(f_{i, x}\) 表示前 \(i\) 个数凑出 \(m \% a_{i + 1} + xa_{i + 1}\) 的方案数
转移先把 \(f_{i - 1, x}\) 进行前缀和,发现本质上是枚举用了多少个 \(a_i\)
此时 \(f_{i - 1, x}\) 的定义发生了变化
把 \(f\) 看成关于 \(x\) 的多项式,取他的系数
根据定义 \(f_{i, x}\) 为转化后的 \(f_{i - 1, xa_{i + 1}/a_{i} + m \% a_{i + 1} / a_{i}}\)
通过拉格朗日插值可以求解
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll read(){
ll x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int mod = 1e6 + 3, maxn = 55;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
ll m, a[maxn];
int n, f[maxn], g[maxn], fac[maxn], ifac[maxn], pre[maxn], suf[maxn];
int lag(int n, int k){
pre[0] = k; suf[n + 1] = 1;
for(int i = 1; i <= n; ++i)pre[i] = 1ll * pre[i - 1] * (k - i) % mod;
for(int i = n; i >= 0; --i)suf[i] = 1ll * suf[i + 1] * (k - i) % mod;
int ans = 0;
for(int i = 0; i <= n; ++i){
int res = 1ll * f[i] * (i ? pre[i - 1] : 1) % mod * suf[i + 1] % mod * ifac[i] % mod * ifac[n - i] % mod;
if((n - i) & 1)res = mod - res;
ans = (ans + res) % mod;
}
ans = (ans + mod) % mod;
return ans;
}
int main(){
freopen("fantasy.in","r",stdin);
freopen("fantasy.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= n; ++i)a[i] = read();
if(m % a[1]){
printf("0\n");
return 0;
}
if(n == 1){
printf("1\n");
return 0;
}
fac[0] = ifac[0] = 1;
for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[n] = qpow(fac[n], mod - 2);
for(int i = n - 1; i >= 1; --i)ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
f[0] = f[1] = 1;
for(int i = 2; i < n; ++i){
for(int j = 1; j < i; ++j) f[j] = (f[j] + f[j - 1]) % mod;
for(int j = 0; j <= i; ++j) g[j] = lag(i - 1, (a[i + 1] / a[i] % mod * j + m % a[i + 1] / a[i]) % mod);
swap(f, g);
}
for(int j = 1; j < n; ++j)f[j] = (f[j] + f[j - 1]) % mod;
printf("%d\n", lag(n - 1, m / a[n] % mod));
return 0;
}
18
A. 拿钱了
\(f_{x, 0 / 1 / 2 / 3, 0 / 1 / 2, 0 / 1 / 2}\)
表示 \(x\) 子树, \(x\) 所在连通块不包含父亲的大小
\(x\) 子树最左侧叶子,不包含\(x\)左侧外叶子的连通块大小
\(x\) 子树最右侧叶子,不包含\(x\)右侧外叶子的连通块大小
的方案数
\(chino\) 巨佬说的
code
#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cassert>
#include<cstring>
#include<complex>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 +(c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int mod = 998244353;
const int maxn = 1e5 + 55;
int n, f[maxn][4][3][3];
vector<int>g[maxn];
void add(int &x, int y){x += y; if(x >= mod) x -= mod;}
void solve(int x, int fa){
if(g[x].size() == 1){
f[x][0][0][0] = f[x][0][2][2] = f[x][0][1][0] = f[x][0][0][1] = f[x][1][0][0] = f[x][2][2][0] = f[x][2][0][2] = 1;
return;
}
f[x][1][0][0] = f[x][1][1][2] = f[x][1][2][1] = 1;
for (int i : g[x])if(i != fa){
solve(i, x);
static int tmp[4][3][3];
memcpy(tmp, f[x], sizeof(tmp));
memset(f[x], 0, sizeof(f[x]));
for (int a = 0; a <= 2; a++)
for (int b = 0; b <= 2; b++)
if(!(a + b) || a + b == 3)
for (int p = 0; p <= 2; p++)
for (int q = 0; q <= 2; q++){
add(f[x][1][p][q], 1ll * tmp[1][p][a] * f[i][0][b][q] % mod);
add(f[x][2][p][q], 1ll * tmp[1][p][a] * f[i][1][b][q] % mod);
add(f[x][2][p][q], 1ll * tmp[2][p][a] * f[i][0][b][q] % mod);
add(f[x][3][p][q], 1ll * tmp[1][p][a] * f[i][2][b][q] % mod);
add(f[x][3][p][q], 1ll * tmp[2][p][a] * f[i][1][b][q] % mod);
add(f[x][3][p][q], 1ll * tmp[3][p][a] * f[i][0][b][q] % mod);
}
}
for(int a = 0; a <= 2; a++)
for(int b = 0; b <= 2; b++)
f[x][0][a][b] = (f[x][1][a][b] + f[x][3][a][b]) % mod;
return;
}
int main(){
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
n = read();
for(int i = 2; i <= n; i++){
int f = read();
g[i].push_back(f);
g[f].push_back(i);
}
if(n <= 2){
printf("%d\n", 1);
return 0;
}
int root = 1;
if(g[root].size() == 1){
root = g[root][0];
for(auto it = g[root].begin(); it != g[root].end(); it++)
if(*it == 1){
g[root].erase(it);
break;
}
g[root].push_back(1);
}
solve(root, 0);
printf("%lld\n",(0ll + f[root][0][0][0] + f[root][0][1][2] + f[root][0][2][1]) % mod);
return 0;
}
B. 打铁了
\(f_{i, x}\) 表示考虑 \(1 - i\), 与最大值不在同一队列的最大值为 \(j\) 的最小代价
转移分情况讨论
使用线段树维护转移
发现一些情况不需要考虑
只需要考虑
当 \(a_i < max\) 时
\([1, a_i)\) 区间加 \(max\)
\((a_i, inf]\) 区间加下标
\(a_{i}\) 为前缀 \(min + a_i\)
维护可能成为前缀 \(min\) 决策点的位置集合,发现需要维护递减的一个序列
对每个决策位置维护他小于后继决策点需要几次区间加下标,满足条件就删去后继
每次单点修改时同样维护集合
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 2e5 + 55;
int n, m, a[maxn], val[maxn];
set<int>s;
ll v[maxn];
struct seg{
struct node{
ll k, b, mi;
}t[maxn << 2 | 1];
void push_up(int x){t[x].mi = min(t[x << 1].mi, t[x << 1 | 1].mi);}
void updk(int x, ll val){t[x].k += val; t[x].mi -= val;}
void updb(int x, ll val){t[x].b += val;}
void push_down(int x){
if(t[x].k)updk(x << 1, t[x].k), updk(x << 1 | 1, t[x].k), t[x].k = 0;
if(t[x].b)updb(x << 1, t[x].b), updb(x << 1 | 1, t[x].b), t[x].b = 0;
}
void build(int x, int l, int r){
if(l == r){
v[l] = l ? (l < m ? 0 : -1) : inf;
t[x].mi = inf;
return;
}
int mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
push_up(x);
}
ll query(int x, int l, int r, int pos){
if(l == r){
v[l] += t[x].b + t[x].k * val[l];
t[x].b = t[x].k = 0;
return v[l];
}
push_down(x);
int mid = (l + r) >> 1;
if(pos <= mid)return query(x << 1, l, mid, pos);
else return query(x << 1 | 1, mid + 1, r, pos);
}
void modify_mi(int x, int l, int r, int pos, ll mi){
if(l == r){
t[x].mi = mi;
return;
}
push_down(x);
int mid = (l + r) >> 1;
if(pos <= mid)modify_mi(x << 1, l, mid, pos, mi);
else modify_mi(x << 1 | 1, mid + 1, r, pos, mi);
push_up(x);
}
void modify_k(int x, int l, int r, int L, int R, ll k){
if(L > R)return;
if(L <= l && r <= R)return updk(x, k);
push_down(x);
int mid = (l + r) >> 1;
if(L <= mid)modify_k(x << 1, l, mid, L, R, k);
if(R > mid)modify_k(x << 1 | 1, mid + 1, r, L, R, k);
push_up(x);
}
void modify_b(int x, int l, int r, int L, int R, ll b){
if(L > R)return;
if(L <= l && r <= R)return updb(x, b);
push_down(x);
int mid = (l + r) >> 1;
if(L <= mid)modify_b(x << 1, l, mid, L, R, b);
if(R > mid)modify_b(x << 1 | 1, mid + 1, r, L, R, b);
push_up(x);
}
void ckmi(int x, int l, int r, int pos, ll nval){
if(l == r){
v[l] += t[x].b + t[x].k * val[l];
t[x].k = t[x].b = 0;
v[l] = min(v[l], nval);
auto it = s.insert(l).first;
int pre = *prev(it), nxt = *next(it);
if(pre != 0){
ll tmp = query(1, 0, m, pre);
modify_mi(1, 0, m, pre, tmp <= nval ? 0 : min((tmp - nval - 1) / (val[pos] - val[pre]) + 1, inf));
}
if(nxt != m){
ll tmp = query(1, 0, m, nxt);
modify_mi(1, 0, m, *it, nval <= tmp ? 0 : min((nval - tmp - 1) / (val[nxt] - val[pos]) + 1, inf));
}
return;
}
push_down(x);
int mid = (l + r) >> 1;
if(pos <= mid)ckmi(x << 1, l, mid, pos, nval);
else ckmi(x << 1 | 1, mid + 1, r, pos, nval);
push_up(x);
}
int find(int x, int l, int r){
if(l == r)return l;
push_down(x);
int mid = (l + r) >> 1;
if(t[x << 1].mi <= 0)return find(x << 1, l, mid);
else return find(x << 1 | 1, mid + 1, r);
}
}T;
ll query(int x){return T.query(1, 0, m, *--s.upper_bound(x));}
ll ans;
int main(){
freopen("stair.in","r",stdin);
freopen("stair.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)a[i] = val[i] = read(), ans -= a[i];
sort(val + 1, val + n + 1); m = unique(val + 1, val + n + 1) - val;
for(int i = 1; i <= n; ++i)a[i] = lower_bound(val + 1, val + m, a[i]) - val;
s.insert(0); s.insert(1); s.insert(m); T.build(1, 0, m);
int mx = val[a[1]];
for(int i = 1; i <= n; ++i, mx = max(mx, val[a[i]])){
if(mx == val[a[i]]){
ans += val[a[i]];
continue;
}
ll tmp = query(a[i]);
T.modify_b(1, 0, m, 1, a[i] - 1, mx);
T.modify_k(1, 0, m, a[i], m - 1, 1);
T.ckmi(1, 0, m, a[i], tmp + val[a[i]]);
while(T.t[1].mi <= 0){
auto it = s.upper_bound(T.find(1, 0, m));
int pre = *prev(it), nxt = *next(it);
ll vp = query(pre), vn = query(nxt);
if(nxt != m)T.modify_mi(1, 0, m, pre, vp <= vn ? 0 : min((vp - vn - 1) / (val[nxt] - val[pre]) + 1, inf));
else T.modify_mi(1, 0, m, pre, inf);
T.modify_mi(1, 0, m, *it, inf);
s.erase(it);
}
}
printf("%lld\n",ans + query(m - 1));
return 0;
}
C. 梦里啥都有
https://www.luogu.com.cn/problem/solution/P6151
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 20005, mod = 1e9 + 7;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
struct poly{
vector<int>f;
void set(int n){f.resize(n);}
int deg(){return f.size();}
int deg()const{return f.size();}
int &operator [](const int &i){return f[i];}
int operator [](const int &i)const{return f[i];}
friend poly operator * (const poly &x, const poly &y){
poly ans;
ans.set(x.deg() + y.deg() - 1);
for(int i = 0; i < x.deg(); ++i)
for(int j = 0; j < y.deg(); ++j)
ans[i + j] = (ans[i + j] + 1ll * x[i] * y[j]) % mod;
return ans;
}
poly operator >> (const int &x) {
poly ans;
ans.f.assign((*this).f.begin() + x, (*this).f.end());
return ans;
}
}f[55];
int n, c[maxn];
int fac[maxn], ifac[maxn];
int C(int n, int m){return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;}
void calc(int i) {
poly x, y;
x.set(c[i] + 1), y.set(c[i] + 1);
for(int j = 1; j <= c[i]; ++j)x[j] = 1ll * C(c[i] + j - 1, 2 * j - 1) * fac[j - 1] % mod;
for(int j = 0; j <= c[i]; ++j)y[c[i] - j] = 1ll * ifac[j] * ((j & 1) ? -1 : 1);
f[i] = (x * y) >> c[i]; f[i].set(c[i] + 1);
for(int j = 1; j <= c[i]; ++j)f[i][j] = 1ll * f[i][j] * ifac[j] % mod * ifac[j - 1] % mod;
f[i][0] = 0;
}
int main(){
freopen("circ.in","r",stdin);
freopen("circ.out","w",stdout);
n = read(); for(int i = 1; i <= n; ++i)c[i] = read();
fac[0] = ifac[0] = 1; for(int i = 1; i <= 10000; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[10000] = qpow(fac[10000], mod - 2); for(int i = 9999; i >= 1; --i)ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
int m = 0; for(int i = 1; i <= n; ++i)m += c[i];
for(int i = 1; i <= n; ++i)calc(i);
for(int i = 2; i <= n; ++i)f[1] = f[1] * f[i];
int ans = 0;
for(int i = 1; i <= m; ++i)ans = (ans + 1ll * f[1][i] * fac[i - 1]) % mod;
ans = 1ll * ans * m % mod;
printf("%d\n",ans);
return 0;
}
19
A. 洛希极限
原题,好消息是这题不卡常
B. 特立独行的图
设值域为 \([-lim, lim]\)
图是一个二分图,两种颜色可以代表正负性
当然,考虑 \(0\) 则可能出现一个三元环
观察性质发现,正数只与负数连边,数越大连边数单调不减
反之亦然
于是按照 \(deg\) 排序进行处理
按正数从小到大处理,每次确定与之相连的负数为 \(now - lim\)
保证所有数不相同则每次 \(++now\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1005, lim = 1e9;
int n, m, col[maxn], st[maxn], ans[maxn], top, now;
vector<int>g[maxn];
bitset<maxn> mp[maxn];
bool odd[maxn];
void dfs(int x){
for(int v : g[x])if(!col[v]){
col[v] = -col[x];
dfs(v);
}
}
int a, b, c;
bool cmp(int x, int y){return y == c || g[x].size() < g[y].size();}
void solve(){
n = read(), m = read();
for(int i = 1; i <= m; ++i){
int u = read(), v = read();
g[u].push_back(v); g[v].push_back(u);
mp[u][v] = mp[v][u] = 1;
}
for(int i = 1; i <= n; ++i)for(int j : g[i])if(j > i){
for(int k : g[j])if(mp[i][k])odd[i] = odd[j] = odd[k] = true;
}
int cnt = 0; a = b = c = 0;
for(int i = 1; i <= n; ++i)if(odd[i]){
++cnt; c = b; b = a; a = i;
}
if(cnt > 3){printf("No\n"); goto X;}
if(cnt){
if(g[a].size() == 2)swap(a, b);
else if(g[c].size() == 2)swap(b, c);
else if(g[b].size() != 2){
printf("No\n"); goto X;
}
}
col[b] = 1;
for(int i = 1; i <= n; ++i)if(!col[i]){
col[i] = 1; dfs(i);
}
for(int i = 1; i <= n; ++i)for(int j : g[i])if(col[i] == col[j] && i != b && j != b){
printf("No\n"); goto X;
}
top = 0;
for(int i = 1; i <= n; ++i)if(col[i] == -1)st[++top] = i;
if(cnt){
if(col[a] == -1)swap(a, c);
if(g[a].size() != top + 1 || g[c].size() != n - top){
printf("No\n"); goto X;
}
}
sort(st + 1, st + top + 1, cmp);
for(int i = 1; i < top; ++i)if((mp[st[i]] | mp[st[i + 1]]) != mp[st[i + 1]]){printf("No\n"); goto X;}
now = 0;
for(int i = 1; i <= top; ++i){
for(int v : g[st[i]])if(ans[v] == 0)ans[v] = (++now) - lim;
ans[st[i]] = ++now;
}
for(int i = 1; i <= n; ++i)if(ans[i] == 0)ans[i] = (++now) - lim;
ans[a] = -lim, ans[b] = 0; ans[c] = lim;
printf("Yes\n");
printf("%d ", lim + lim);
for(int i = 1; i <= n; ++i)printf("%d ", ans[i]); printf("\n");
for(int i = 1; i <= n; ++i)ans[i] = 0;
X:;
for(int i = 1; i <= n; ++i)g[i].clear();
for(int i = 1; i <= n; ++i)mp[i].reset();
for(int i = 1; i <= n; ++i)odd[i] = col[i] = 0;
}
int main(){
// freopen("graph.in","r",stdin);
// freopen("graph.out","w",stdout);
int t = read(); for(int i = 1; i <= t; ++i)solve();
return 0;
}
C. 玩游戏
推荐学长的博客
学长那个式子需要特殊处理 \(k >= n\) 的情况好像
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
ll n; int k;
ld H(ll x){
if(x <= 1e5){
ld ans = 0;
for(int i = 1; i <= x; ++i)ans += (ld)1 / i;
return ans;
}
return logl(x) + (ld)1 / (x + x) + 0.57721566490153286060651209;
}
int main() {
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
scanf("%d%lld",&k, &n);
ld ans = H(n + k) - H(k);
for(int i = 1; i <= k; i++) ans = ans * (n + i) / i;
char tmp[20]; int p10;
sprintf(tmp, "%.9Le", ans);
sscanf(tmp + 13, "%d", &p10), tmp[13] = '\0';
printf("%s%03d\n", tmp, p10);
return 0;
}
20
A. 回文划分
贪心的选择最短的相同前后缀即可
证明如果 \(ad = ca\) 最优
那么可以写成 \(ad'a=ac'a\)
得到 \(c = d\)
那么先去掉 \(a\),再去掉 \(c / d\) 一定比直接去掉 \(ad\)更优
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e6 + 55, base = 29, mod = 998244353;
int n;
char s[maxn];
void sol(){
scanf("%s",s + 1); n = strlen(s + 1);
ll s1 = 0, s2 = 0, b = 1; int ans = 0;
for(int i = 1; i <= n / 2; ++i){
s1 = (s1 * base + (s[i] - 'a' + 1)) % mod;
s2 = (s2 + b * (s[n - i + 1] - 'a' + 1)) % mod;
b = b * base % mod;
if(s1 == s2){
ans += 2; s1 = s2 = 0; b = 1;
}
}
if(s1 || (n & 1))++ans;
printf("%d\n",ans);
}
int main(){
// freopen("divide.in","r",stdin);
// freopen("divide.out","w",stdout);
int t; scanf("%d",&t);
for(int i = 1; i <= t; ++i)sol();
return 0;
}
B. 吉利售价
没看出是数论题
求 \(x 10^k + dddd...(k个) \equiv 0 \mod b\)
可以解同余方程求解
发现 \(x <= b\) 即可,所以可以枚举位数等于 \(a\) 的
对于位数小于 \(a\) 的,看看是否存在解即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e6 + 55;
int p, d, n, m, tp[maxn], rd[maxn];
char a[maxn];
int calc(int x){
int ans = 0;
while(x && x % 10 == d)++ans, x /= 10;
return ans;
}
int main(){
freopen("price.in","r",stdin);
freopen("price.out","w",stdout);
scanf("%d%d",&p,&d); int pp = p; while(pp){++m; pp /= 10;}
scanf("%s",a + 1); n = strlen(a + 1);
tp[0] = 1;
for(int i = 1; i <= n; ++i){
tp[i] = tp[i - 1] * 10 % p;
rd[i] = (rd[i - 1] + d * tp[i - 1]) % p;
}
bool flag = false;
for(int i = m + 1; i <= n; ++i)
if(d > a[i] - '0'){flag = true; break;}
else if(d < a[i] - '0'){flag = false; break;}
int res = 0;
for(int i = 1; i <= m; ++i)res = res * 10 + a[i] - '0';
int ans = 0;
for(int i = 0; i <= res - flag; ++i){
if((1ll * i * tp[n - m] + rd[n - m]) % p == 0 && (i || d))ans = max(ans, n - m + calc(i));
}
for(int i = n - m - 1; i >= ans; --i)if((p - rd[i]) % __gcd(tp[i], p) == 0)ans = max(ans, i);
printf("%d\n",ans);
return 0;
}
C. 首尾匹配
对正串和反串建 \(Trie\)
问题是查询同时在正串某节点子树内和反串某节点子树内的字符串数量
这是二维数点问题,在一个树上 \(dfs\), 另外一棵用 \(BIT\) 维护 \(dfs\) 序上的答案即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 2e6 + 55;
int n, m, sum;
int id(char c){
if(c == 'A')return 0;
if(c == 'G')return 1;
if(c == 'U')return 2;
if(c == 'C')return 3;
assert(0);
}
struct BIT{
int t[maxn];
int lowbit(int x){return x & -x;}
void add(int x){while(x <= sum){++t[x]; x += lowbit(x);}}
int query(int x){int ans = 0; while(x){ans += t[x]; x -= lowbit(x);} return ans;}
}t;
char s[maxn], c[maxn];
struct suf_Trie{
int ch[maxn][4], cnt = 1, root = 1;
int ins(int len){
int now = root;
for(int i = 1; i <= len; ++i){
if(!ch[now][id(c[i])])ch[now][id(c[i])] = ++cnt;
now = ch[now][id(c[i])];
}
return now;
}
int querypos(int len){
int now = root;
for(int i = 1; i <= len; ++i){
if(!ch[now][id(c[i])])return 0;
now = ch[now][id(c[i])];
}
return now;
}
int dfn[maxn], tim, dfnr[maxn];
void dfs(int x){
dfn[x] = ++tim;
for(int i = 0; i < 4; ++i)if(ch[x][i])dfs(ch[x][i]);
dfnr[x] = tim;
}
void add(int pos){t.add(dfn[pos]);}
int query(int x){return t.query(dfnr[x]) - t.query(dfn[x] - 1);}
}suf;
struct que{int pos, id;};
vector<que>q[maxn];
int ans[maxn];
struct Trie{
int ch[maxn][4], cnt = 1, root = 1; vector<int>rem[maxn];
void ins(int len, int pos){
int now = root;
for(int i = 1; i <= len; ++i){
if(!ch[now][id(c[i])])ch[now][id(c[i])] = ++cnt;
now = ch[now][id(c[i])];
}
rem[now].push_back(pos);
}
int query(int len){
int now = root;
for(int i = 1; i <= len; ++i){
if(!ch[now][id(c[i])])return 0;
now = ch[now][id(c[i])];
}
return now;
}
void solve(int x){
for(que v : q[x])ans[v.id] -= suf.query(v.pos);
for(int v : rem[x])suf.add(v);
for(int i = 0; i < 4; ++i)if(ch[x][i])solve(ch[x][i]);
for(que v : q[x])ans[v.id] += suf.query(v.pos);
}
}pre;
int main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i){
scanf("%s",s + 1); int len = strlen(s + 1);
for(int j = 1; j <= len; ++j)c[j] = s[len - j + 1]; int pos = suf.ins(len);
for(int j = 1; j <= len; ++j)c[j] = s[j]; pre.ins(len, pos);
}
suf.dfs(1); sum = suf.cnt;
for(int i = 1; i <= m; ++i){
scanf("%s",s + 1); int len = strlen(s + 1);
for(int j = 1; j <= len; ++j)c[j] = s[j];
int pos = pre.query(len);
scanf("%s",s + 1); len = strlen(s + 1);
for(int j = 1; j <= len; ++j)c[j] = s[len - j + 1];
int qp = suf.querypos(len);
if(pos && qp)q[pos].push_back({qp, i});
}
pre.solve(1);
for(int i = 1; i <= m; ++i)printf("%d\n",ans[i]);
return 0;
}
D. 修水管
来自一场被遗忘的考试
https://www.cnblogs.com/Chencgy/p/16723912.html
我还有一个优雅的题面来着
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int maxn = 255;
int n, r;
db p[maxn], f[maxn][maxn];
db qpow(db x, int y){
db ans = 1;
for(; y; y >>= 1, x = x * x)if(y & 1)ans = ans * x;
return ans;
}
int d[maxn];
void solve(){
scanf("%d%d",&n,&r);
for(int i = 1; i <= n; ++i)scanf("%lf%d",&p[i], &d[i]);
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= r; ++j)
f[i][j] = 0;
f[1][0] = qpow((1 - p[1]), r);
f[1][1] = 1 - f[1][0];
for(int i = 2; i <= n; ++i){
int up = min(r, i);
for(int j = 1; j <= up; ++j)
f[i][j] += f[i - 1][j - 1] * (1 - qpow(1 - p[i], r - j + 1)) + f[i - 1][j] * qpow(1 - p[i], r - j);
f[i][0] = f[i - 1][0] * qpow(1 - p[i], r);
}
db ans = f[1][1] * d[1];
for(int i = 2; i <= n; ++i){
int up = min(r, i);
db g = 0;
for(int j = 0; j <= up; ++j) g += f[i - 1][j] * (1 - qpow(1 - p[i], r - j));
ans += g * d[i];
}
printf("%.10lf\n",ans);
}
int main(){
freopen("pipe.in","r",stdin);
freopen("pipe.out","w",stdout);
int t; scanf("%d",&t);
for(int i = 1; i <= t; ++i)solve();
return 0;
}