CSP-S 模拟19/10/30

T1:给定两个长度为 nnn 的序列 a,ba,ba,b 你需要选择一个区间
[l,r][l, r][l,r],使得 al+…+ar≥0a_l+…+a r \ge 0al++ar0bl+…+br≥0b l +…+b r \ge 0bl++br0 。最大化你选择的区间长度
解:枚举右端点,查最小的左端点
需要 sumar−suml−1≥0,sumbr−sumbl−1≥0suma_r-sum_{l-1}\ge 0,sumb_r-sumb_{l-1}\ge 0sumarsuml10,sumbrsumbl10
就是一个三维偏序,树套树
发现枚举的 rrr 这一维没有用,因为如果查到了一个 ≥r\ge rrlll 是不会影响的
于是按 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_idepivalin≤5e3n\le 5e3n5e3
考虑区间 dpdpdpfl,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,p1+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,r1],[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 300n300


题挺好的,部分分很足,先来讲一讲
对于 %25\%25%25 的数据, n≤50n\le 50n50 ,暴力枚举终点高斯消元

对于另外 %15\%15%15 的数据,不存在 u,vu,vu,v 使得 u!=vu!=vu!=vmin(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-1m1 条边满足 u<vu<vu<v
发现存在 n−1n-1n1 行,假设它是第 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,i1][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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值