T1:给定两个长度为 nnn 的序列 a,ba,ba,b 你需要选择一个区间
[l,r][l, r][l,r],使得 al+…+ar≥0a_l+…+a r \ge 0al+…+ar≥0 且 bl+…+br≥0b l +…+b r \ge 0bl+…+br≥0 。最大化你选择的区间长度
解:枚举右端点,查最小的左端点
需要 sumar−suml−1≥0,sumbr−sumbl−1≥0suma_r-sum_{l-1}\ge 0,sumb_r-sumb_{l-1}\ge 0sumar−suml−1≥0,sumbr−sumbl−1≥0
就是一个三维偏序,树套树
发现枚举的 rrr 这一维没有用,因为如果查到了一个 ≥r\ge r≥r 的 lll 是不会影响的
于是按 sumasumasuma 排序,sumbsumbsumb 树状数组即可
#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 1e6 + 5;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
int n;
struct node{ ll a, b; int id; } x[N];
bool cmp(node A, node B){ return A.a < B.a; }
ll c[N]; int siz;
cs int inf = 1e9;
struct BIT{
int c[N];
BIT(){ memset(c, 0x3f, sizeof(c)); }
void add(int x, int v){ for(;x<=siz;x+=x&-x) c[x] = min(c[x], v); }
int ask(int x){ int ans = inf; for(;x;x-=x&-x) ans = min(ans, c[x]); return ans; }
}bit;
int main(){
n = read(); x[0].a = x[0].b = 0; x[0].id = 0; c[++siz] = 0;
for(int i = 1; i <= n; i++) x[i].a = x[i-1].a + (ll)read(), x[i].id = i;
for(int i = 1; i <= n; i++) x[i].b = x[i-1].b + (ll)read(), c[++siz] = x[i].b;
sort(x, x + n + 1, cmp);
sort(c + 1, c + siz + 1); siz = unique(c + 1, c + siz + 1) - (c + 1);
int ans = 0;
for(int i = 0; i <= n; i++){
int p = lower_bound(c + 1, c + siz + 1, x[i].b) - c;
ans = max(ans, x[i].id - bit.ask(p));
bit.add(p, x[i].id);
} cout << ans; return 0;
}
T2:给出一个二叉树的中序遍历的权值,最小化 ∑depi∗vali\sum dep_i*val_i∑depi∗vali,n≤5e3n\le 5e3n≤5e3
考虑区间 dpdpdp,fl,rf_{l,r}fl,r 表示把 [l,r][l,r][l,r] 建成一颗二叉树的最小代价
枚举根 ppp,发现根的新增贡献为 wpw_pwp,两边的每个点的新增贡献为它们的权值,因为 depdepdep 增加 1
于是有 fl,r=min(fl,p−1+fp+1,r+sum(l,r))f_{l,r}=min(f_{l,p-1}+f_{p+1,r}+sum(l,r))fl,r=min(fl,p−1+fp+1,r+sum(l,r))
看数据范围盲猜决策单调性,其实不用盲猜,你 rrr 挪到 r+1r+1r+1 比较显然 根也会往右移
比较严谨的证明是首先 sumsumsum 满足四边形不等式,所以可以证明 fff 满足四边形不等式,由于 fff 满足四边形不等式,具体可以参考 lyd 的书
所以 [l,r][l,r][l,r] 的决策点在 [l,r−1],[l+1,r][l,r-1],[l+1,r][l,r−1],[l+1,r] 的中间,复杂度 O(n2)O(n^2)O(n2)
#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 5e3 + 5;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
typedef long long ll;
int n; ll sum[N], f[N][N];
int p[N][N]; // 决策点
int main(){
n = read();
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (ll)read();
for(int l = n; l >= 1; l--){
for(int r = l; r <= n; r++){
f[l][r] = sum[r] - sum[l - 1];
if(l == r){ p[l][r] = l; continue; }
ll ret = 1e15;
for(int k = p[l][r-1]; k <= p[l+1][r]; k++){
if(f[l][k - 1] + f[k + 1][r] < ret){
p[l][r] = k; ret = f[l][k - 1] + f[k + 1][r];
}
} f[l][r] += ret;
}
} cout << f[1][n]; return 0;
}
T3:
给定一张 nnn 个点 mmm 条边的强连通有向图。
初始时你在 111 号点,你会不停地从当前点的所有出边中等概率随机一条走过
去。当你到达 k 号点的时候,你会栽跟头并且停止走路
对于 k∈[2,n]k\in[2,n]k∈[2,n] ,分别求出你期望要走多少条边
n≤300n\le 300n≤300
题挺好的,部分分很足,先来讲一讲
对于 %25\%25%25 的数据, n≤50n\le 50n≤50 ,暴力枚举终点高斯消元
对于另外 %15\%15%15 的数据,不存在 u,vu,vu,v 使得 u!=vu!=vu!=v 且 min(u,v)>1min(u,v)>1min(u,v)>1
这个点是一个菊花图,也就是说在高斯消元的矩阵上,第一行每一列都有值
第iii 列只有 1 和 iii 有值,所以把 i∈[2,n]i\in[2,n]i∈[2,n] 的每一行的 iii 去把第一行的消掉,复杂度 O(n)O(n)O(n)
加上枚举是 O(n2)O(n^2)O(n2) 的
对于另外 %20\%20%20 的数据,前 m−1m-1m−1 条边满足 u<vu<vu<v
发现存在 n−1n-1n−1 行,假设它是第 iii 行,那么第 iii 列之前是没有值的
也就是说给出来的矩阵是一个比较完美的上三角矩阵,O(n2)O(n^2)O(n2) 消元即可
正解:发现对于一个终点 TTT,做法是把 TTT 一行忽略掉,也就是它不参与消元
所以对于 T∈[l,mid]T\in[l,mid]T∈[l,mid],(mid,r](mid,r](mid,r] 都是参与了消元了的,分治即可
也可以这么理解,对于一个一行 iii,它能去参与消元当且仅当 i!=Ti!=Ti!=T,也就是说 iii 可以参与 [1,i−1][i+1,n][1,i-1][i+1,n][1,i−1][i+1,n] 的消元,线段树分治即可,相对更好写
这里需要把高斯消元的板子变一下形,因为并不是从第一行开始消的:
对于第一个考虑的行 iii,把它挪到第一行,消去其它行的第 iii 列
这样是 O(n2)O(n^2)O(n2) 的
也就是说我们按消除行的顺序输出消完的矩阵是一个上三角矩阵
不需要重排,记录一下第一行的实际值是什么即可
递归到底层的时候需要对上三角矩阵求解,是 O(n2)O(n^2)O(n2) 的,每一个算一遍是 O(n3)O(n^3)O(n3)
而一个值会在线段树上有 lognlog_nlogn 个区间,所以它会去消 lognlog_nlogn 次
然后有一个头疼的逆元,需要快速幂,复杂度 O(n3log2(n))O(n^3log^2(n))O(n3log2(n))
主要考察的是一类分治问题的套路与灵活运用高斯消元
等等,方程还没有列 fu=1+∑fvdeguf_{u}=1+\sum \frac{f_v}{deg_u}fu=1+∑degufv
由于递归完了要还原,每一次开一个 tmptmptmp 数组即可
#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 305, M = 1e5 + 5;
cs int Mod = 998244353;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt * 10 + (ch-'0'), ch = getchar();
return cnt * f;
}
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b;}
int mul(int a, int b){ return 1ll * a * b % Mod; }
int ksm(int a, int b){ int ans = 1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans = mul(ans, a); return ans; }
int n, m, du[N];
int first[N], nxt[M], to[M], tot;
void adde(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y; }
int a[N][N], tp[20][N][N], ans[N];
bool vis[N];
int top, idx[N]; bool Svis[20][N];
void Gauss(int i){ // 用第 i 行去消其它的
vis[i] = true;
idx[++top] = i;
for(int j = 1; j <= n; j++){
if(vis[j]) continue;
int delta = mul(a[j][i], ksm(a[i][i], Mod-2));
if(!delta) continue;
for(int k = 1; k <= n+1; k++){
a[j][k] = add(a[j][k], Mod - mul(delta, a[i][k]));
}
}
}
int b[N][N];
int calc(){
for(int i = n; i >= 1; i--){
if(!vis[idx[i]]) continue;
for(int j = i + 1; j <= n; j++) {
if(!vis[idx[j]]) continue;
a[idx[i]][n + 1] = add(a[idx[i]][n + 1], Mod - mul(a[idx[i]][idx[j]], a[idx[j]][n + 1]));
} a[idx[i]][n + 1] = mul(a[idx[i]][n + 1], ksm(a[idx[i]][idx[i]], Mod - 2));
}
return a[idx[1]][n + 1];
}
#define mid ((l+r)>>1)
vector<int> v[N << 2];
void Push(int x, int l, int r, int L, int R, int p){
if(L>R) return;
if(L<=l && r<=R){ v[x].push_back(p); return; }
if(L<=mid) Push(x<<1, l, mid, L, R, p);
if(R>mid) Push(x<<1|1, mid+1, r, L, R, p);
}
void Solve(int dep, int x, int l, int r){
memcpy(tp[dep], a, sizeof(a));
memcpy(Svis[dep], vis, sizeof(vis));
for(int i = 0; i < v[x].size(); i++){
int p = v[x][i]; Gauss(p);
}
if(l == r){ if(l^1) ans[l] = calc(); }
else{
Solve(dep + 1, x << 1, l, mid);
Solve(dep + 1, x << 1|1, mid+1, r);
}
memcpy(a, tp[dep], sizeof(tp[dep]));
memcpy(vis, Svis[dep], sizeof(Svis[dep]));
for(int i = 0; i < v[x].size(); i++) idx[top--] = 0;
}
int main(){
n = read(), m = read();
for(int i = 1; i <= m; i++){
int x = read(), y = read();
adde(x, y); ++du[x];
}
for(int i = 1; i <= n; i++){
a[i][i] = 1;
int inv = ksm(du[i], Mod - 2);
for(int e = first[i]; e; e = nxt[e]){
int t = to[e]; a[i][t] = add(a[i][t], Mod - inv);
} a[i][n + 1] = 1;
}
Push(1, 1, n, 1, n, 1);
for(int i = 2; i <= n; i++) Push(1, 1, n, 1, i-1, i), Push(1, 1, n, i+1, n, i);
Solve(0, 1, 1, n);
for(int i = 2; i <= n; i++) cout << ans[i] << '\n';
return 0;
}
2万+

被折叠的 条评论
为什么被折叠?



