T1:给定两个长度为 的序列
你需要选择一个区间
,使得
且
。最大化你选择的区间长度
解:枚举右端点,查最小的左端点
需要
就是一个三维偏序,树套树
发现枚举的 这一维没有用,因为如果查到了一个
的
是不会影响的
于是按 排序,
树状数组即可
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:给出一个二叉树的中序遍历的权值,最小化 ,
考虑区间 ,
表示把
建成一颗二叉树的最小代价
枚举根 ,发现根的新增贡献为
,两边的每个点的新增贡献为它们的权值,因为
增加 1
于是有
看数据范围盲猜决策单调性,其实不用盲猜,你 挪到
比较显然 根也会往右移
比较严谨的证明是首先 满足四边形不等式,所以可以证明
满足四边形不等式,由于
满足四边形不等式,具体可以参考 lyd 的书
所以 的决策点在
的中间,复杂度
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:
给定一张 个点
条边的强连通有向图。
初始时你在 号点,你会不停地从当前点的所有出边中等概率随机一条走过
去。当你到达 k 号点的时候,你会栽跟头并且停止走路
对于 ,分别求出你期望要走多少条边
题挺好的,部分分很足,先来讲一讲
对于 的数据,
,暴力枚举终点高斯消元
对于另外 的数据,不存在
使得
且
这个点是一个菊花图,也就是说在高斯消元的矩阵上,第一行每一列都有值
第 列只有 1 和
有值,所以把
的每一行的
去把第一行的消掉,复杂度
加上枚举是 的
对于另外 的数据,前
条边满足
发现存在 行,假设它是第
行,那么第
列之前是没有值的
也就是说给出来的矩阵是一个比较完美的上三角矩阵, 消元即可
正解:发现对于一个终点 ,做法是把
一行忽略掉,也就是它不参与消元
所以对于 ,
都是参与了消元了的,分治即可
也可以这么理解,对于一个一行 ,它能去参与消元当且仅当
,也就是说
可以参与
的消元,线段树分治即可,相对更好写
这里需要把高斯消元的板子变一下形,因为并不是从第一行开始消的:
对于第一个考虑的行 ,把它挪到第一行,消去其它行的第
列
这样是 的
也就是说我们按消除行的顺序输出消完的矩阵是一个上三角矩阵
不需要重排,记录一下第一行的实际值是什么即可
递归到底层的时候需要对上三角矩阵求解,是 的,每一个算一遍是
而一个值会在线段树上有 个区间,所以它会去消
次
然后有一个头疼的逆元,需要快速幂,复杂度
主要考察的是一类分治问题的套路与灵活运用高斯消元
等等,方程还没有列
由于递归完了要还原,每一次开一个 数组即可
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];
}
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;
}