BJOI 2019 题解

[BJOI2019]奥术神杖
容易发现这是一个 A C AC AC 自动机带 d p dp dp 的比较套路的题
现在的问题就是最大化一个根号下面的东西
考虑乘法变加法去个 l n ln ln l n ( ∏ i = 1 k a i ) 1 k = 1 k ∑ i = 1 k l n ( a i ) ln (\prod_{i=1}^k a_i)^{\frac{1}{k}}=\frac{1}{k}\sum_{i=1}^kln(a_i) ln(i=1kai)k1=k1i=1kln(ai)
然后就是最大化后面那个式子,分数规划即可

#include<bits/stdc++.h>
#define N 1550
#define eps 1e-3
using namespace std;
int n, m, res;
int ch[N][10], fail[N], siz[N], tot, g[N][N], h[N][N];
double f[N][N], w[N];
char T[N], ans[N];
void Insert(string s, int v){
	int now = 0, len = s.length(); 
	for(int i=0; i<len; i++){
		int x = s[i] - '0';
		if(!ch[now][x]) ch[now][x] = ++tot;
		now = ch[now][x];
	} w[now] = log(v), siz[now]++;
}
void Build(){
	queue<int> q;
	for(int i=0; i<10; i++)
		if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty()){
		int x = q.front(); q.pop();
		w[x] += w[fail[x]], siz[x] += siz[fail[x]];
		for(int i=0; i<10; i++){
			if(ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]);
			else ch[x][i] = ch[fail[x]][i];
		}
	}
}
void Modify(int i, int j, int k){
	int c = ch[j][k];
	if(f[i][c] < f[i-1][j] + w[c]) f[i][c] = f[i-1][j] + w[c], g[i][c] = j, h[i][c] = k; 
}
bool check(double mid){
	for(int i=0; i<=tot; i++) w[i] -= mid * siz[i];
	for(int i=0; i<=n; i++)
		for(int j=0; j<=tot; j++)
			f[i][j] = - 1e17;
	f[0][0] = 0;
	for(int i=1; i<=n; i++){
		for(int j=0; j<=tot; j++){
			if(T[i] == '.') for(int k=0; k<10; k++) Modify(i, j, k);
			else Modify(i, j, T[i]-'0');
		}
	} 
	double ans = - 1e17;
	for(int i=0; i<=tot; i++) ans = max(ans, f[n][i]);
	for(int i=0; i<=tot; i++) w[i] += mid * siz[i];
	return ans > 0;
}
void dfs(int i, int j){ if(i == 0) return; dfs(i-1, g[i][j]); cout<<h[i][j];}
int main(){
	scanf("%d%d", &n, &m); res = n;
	scanf("%s", T+1);
	for(int i=1; i<=m; i++){
		string s; int x; cin >> s >> x; 
		Insert(s, x);
	} Build(); 
	double l = 0, r = 25;
	while((r-l) > eps){
		double mid = (l+r)/2;
		if(check(mid)) l = mid;
		else r = mid;
	} check(l); double mx = -1e17; int pos = 0;
	for(int i=0; i<=tot; i++) if(f[n][i] > mx) mx = f[n][i], pos = i;
	dfs(n, pos); return 0;
}

[BJOI2019]勘破神机
很妙的数学题
首先 2 ∗ n 2*n 2n 的情况就是斐波那契
于是,令 f i f_i fi 为斐波那契数列
a n s = ∑ i = l r ( f i k ) ans=\sum_{i=l}^r\binom{f_i}{k} ans=i=lr(kfi)
考虑到 1 k ! \frac{1}{k!} k!1 是一个定值,而开出去过后是一个下降幂,是不是可以用斯特林化简
a n s = ∑ i = l r ( f i k ) ans=\sum_{i=l}^r\binom{f_i}{k} ans=i=lr(kfi)
= 1 k ! ∑ i = l r ∑ j = 0 k ( − 1 ) k − j ∗ s k , j ∗ f i j =\frac{1}{k!}\sum_{i=l}^r \sum_{j=0}^k (-1)^{k-j}*s_{k,j}*f_i^j =k!1i=lrj=0k(1)kjsk,jfij
考虑到 f n = 1 5 ( 1 + 5 2 ) n − 1 5 ( 1 − 5 2 ) n f_n=\frac{1}{\sqrt 5}(\frac{1+\sqrt 5}{2})^n-\frac{1}{\sqrt 5}(\frac{1-\sqrt 5}{2})^n fn=5 1(21+5 )n5 1(215 )n
f n = A x n + B y n f_n=Ax^n+By^n fn=Axn+Byn,那么原式为
= 1 k ! ∑ i = l r ∑ k = 0 j ( − 1 ) k − j ∗ s k , j ∗ ( A x i + B y i ) j =\frac{1}{k!}\sum_{i=l}^r\sum_{k=0}^j(-1)^{k-j}*s_{k,j}*(Ax^i+By^i)^j =k!1i=lrk=0j(1)kjsk,j(Axi+Byi)j
= 1 k ! ∑ k = 0 j ( − 1 ) k − j ∗ s k , j ∗ ∑ i = l r ( A x i + B y i ) j =\frac{1}{k!}\sum_{k=0}^j(-1)^{k-j}*s_{k,j}*\sum_{i=l}^r(Ax^i+By^i)^j =k!1k=0j(1)kjsk,ji=lr(Axi+Byi)j
暴力展开
= 1 k ! ∑ k = 0 j ( − 1 ) k − j ∗ s k , j ∗ ∑ i = l r ∑ l = 0 j ( j l ) ( A ∗ x i ) l ∗ ( B ∗ y i ) j − l =\frac{1}{k!}\sum_{k=0}^j(-1)^{k-j}*s_{k,j}*\sum_{i=l}^r\sum_{l=0}^j\binom{j}{l} (A*x^i)^l*(B*y^i)^{j-l} =k!1k=0j(1)kjsk,ji=lrl=0j(lj)(Axi)l(Byi)jl
= 1 k ! ∑ k = 0 j ( − 1 ) k − j ∗ s k , j ∗ ∑ l = 0 j ( j l ) A l B j − l ∑ i = l r x i l ∗ y i ∗ ( j − l ) =\frac{1}{k!}\sum_{k=0}^j(-1)^{k-j}*s_{k,j}*\sum_{l=0}^j\binom{j}{l} A^lB^{j-l}\sum_{i=l}^r x^{il}*y^{i*(j-l)} =k!1k=0j(1)kjsk,jl=0j(lj)AlBjli=lrxilyi(jl)
发现后面一坨就是
∑ i = l r x i l ∗ y i ∗ ( j − l ) = ∑ ( x l y j − l ) i \sum_{i=l}^r x^{il}*y^{i*(j-l)}=\sum (x^ly^{j-l})^i i=lrxilyi(jl)=(xlyjl)i
等比数列
到这里复杂度是 O ( n 2 l o g ( n ) ) O(n^2log(n)) O(n2log(n))
5 \sqrt 5 5 直接带复数运算

考虑 3 个的情况,显然只有偶数的时候有答案,于是我们每两列分一块
首先由 f i = f i − 1 ∗ 3 f_i=f_{i-1}*3 fi=fi13
然后还有跨块的情况
方案数是 2 ∗ ∑ j = 0 i − 2 f i 2*\sum_{j=0}^{i-2} f_{i} 2j=0i2fi
不想写了,把 n + 1 n+1 n+1 写出来,做差,解特征方程,求通项就和上面一样了

#include<bits/stdc++.h>
#define N 505
using namespace std;
typedef long long ll;
const int Mod = 998244353, inv2 = (Mod+1)/2;
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 power(int a, int b){int ans=1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans;}
int inv(int x){ return power(x, Mod-2);}
const int inv5 = inv(5), inv6 = inv(6);
int T, m;
ll l, r; int k;
int c[N + 10][N + 10], s[N + 10][N + 10], fac[N + 10];

#define cs const
struct data{
	int x, y;
	data(int _x = 0, int _y = 0){ x = _x; y = _y;}
	data operator + (cs data &a){ return data(add(x, a.x), add(y, a.y));}
	data operator - (cs data &a){ return data(add(x, Mod-a.x), add(y, Mod-a.y));}
	data operator * (cs data &a){ return data(add(mul(x,a.x), mul(m, mul(y,a.y))), add(mul(x, a.y), mul(y, a.x)));}
	data operator * (cs int &a){ return data(mul(x,a), mul(y,a));}
}X, Y, A, B;
data Inv(data a){ return data(a.x, Mod-a.y) * inv(add(mul(a.x,a.x), Mod-mul(m, mul(a.y,a.y))));}
data ksm(data a, ll b){ data ans(1, 0); for(;b;b>>=1,a=a*a) if(b&1) ans=ans*a; return ans;}

void prework(){
	c[0][0] = 1;
	for(int i = 1; i <= N ; i++){
		c[i][0] = 1; for(int j = 1; j <= i; j++) c[i][j] = add(c[i-1][j-1], c[i-1][j]);
	}
	s[0][0] = 1;
	for(int i = 1; i <= N; i++){
		for(int j = 1; j <= i; j++) s[i][j] = add(s[i-1][j-1], mul(s[i-1][j], i-1));
	}
	fac[0] = fac[1] = 1;
	for(int i = 1; i <= N; i++) fac[i] = mul(fac[i-1], i);
}
void FSY(){
	l++,r++;
	X = data(inv2, inv2); Y = data(inv2, Mod - inv2);
	A = data(0, inv5); B = data(0, Mod - inv5);
	int ans = 0;
	for(int i = 0; i <= k; i++){
		int now = ((k-i)&1) ? Mod-s[k][i] : s[k][i];
		data res(0, 0);
		for(int j = 0; j <= i; j++){
			data tmp = ksm(X, j) * ksm(Y, i-j); 
			data val = (ksm(tmp, r+1) - ksm(tmp, l)) * Inv(tmp - data(1,0));
			if(tmp.x == 1 && tmp.y == 0) val = tmp * ((r-l+1) % Mod);
			res = res + ((ksm(A, j) * ksm(B, i-j) * val) * c[i][j]);
		} ans = add(ans, mul(now, res.x));
	} cout << mul(inv((r-l+1) % Mod), mul(inv(fac[k]), ans)) << '\n';
}
void Yolanda(){
	int Yol = inv((r-l+1) % Mod);
	l = (l + 1) >> 1, r = r >> 1;
	X = data(2, 1); Y = data(2, Mod-1);
	A = data(inv2, inv6); B = data(inv2, Mod - inv6);
	int ans = 0;
	for(int i = 0; i <= k; i++){
		int now = ((k-i)&1) ? Mod-s[k][i] : s[k][i];
		data res(0, 0);
		for(int j = 0; j <= i; j++){
			data tmp = ksm(X, j) * ksm(Y, i-j); 
			data val = (ksm(tmp, r+1) - ksm(tmp, l)) * Inv(tmp - data(1,0));
			if(tmp.x == 1 && tmp.y == 0) val = tmp * ((r-l+1) % Mod);
			res = res + ((ksm(A, j) * ksm(B, i-j) * val) * c[i][j]);
		} ans = add(ans, mul(now, res.x));
	} cout << mul(Yol, mul(inv(fac[k]), ans)) << '\n';
}
int main(){
	prework();
	scanf("%d%d", &T, &m);
	if(m == 2) m = 5;
	while(T--){
		scanf("%lld%lld%d", &l, &r, &k);
		if(m == 5) FSY(); else Yolanda();
	}
}

[BJOI2019]排兵布阵
a i a_i ai 排序后 d p dp dp 方程显然
f j = m a x ( f j , f j − 2 ∗ a i , k − 1 + i k ) f_j=max(f_j,f_{j-2*a_{i,k}-1}+ik) fj=max(fj,fj2ai,k1+ik)

[BJOI2019]光线
f , g f,g f,g 分别表示向下向上穿透的概率
f i = f i − 1 ∗ a i + g i + 1 ∗ b i f_i=f_{i-1}*a_i+g_{i+1}*b_i fi=fi1ai+gi+1bi
g i = f i − 1 ∗ b i + g i + 1 ∗ a i g_i=f_{i-1}*b_i+g_{i+1}*a_i gi=fi1bi+gi+1ai
f n = f n − 1 ∗ a n f_n=f_{n-1}*a_n fn=fn1an
g n = f n − 1 ∗ b n g_n=f_{n-1}*b_n gn=fn1bn
这个时候会发现非常的绞,这也是这道题的难点,考虑从后两个式子寻找突破口
发现 f n f_n fn 可以用 f n − 1 f_{n-1} fn1 表示, g n g_n gn 可以用 f n − 1 f_{n-1} fn1 表示
这启示我们继续考虑 n − 1 n-1 n1
发现 f n − 1 = f n − 2 ∗ a n − 1 + ( f n − 1 ∗ b n ) ∗ a n − 1 f_{n-1}=f_{n-2}*a_{n-1}+(f_{n-1}*b_n)*a_{n-1} fn1=fn2an1+(fn1bn)an1
也就是说 f n − 1 f_{n-1} fn1 可以用 f n − 2 f_{n-2} fn2 表示
于是我们就可以待定系数,先 f i f_i fi 为多少倍的 f i − 1 f_{i-1} fi1,以及 g i g_i gi 是多少倍的 f i − 1 f_{i-1} fi1
最后从头推回去,感觉有点妙

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int Mod = 1e9 + 7;
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; }
cs int inv = ksm(100, Mod - 2);
cs int N = 5e5 + 5;
int a[N], b[N], f[N], g[N], n;
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d%d", &a[i], &b[i]);
		a[i] = mul(a[i], inv);
		b[i] = mul(b[i], inv);
	}
	for(int i = n; i >= 1; i--){
		f[i] = mul(a[i], ksm(Mod+1-mul(g[i+1],b[i]), Mod-2));
		g[i] = add(b[i], mul(f[i],mul(g[i+1],a[i])));
	} 
	for(int i = 2; i <= n; i++) f[i] = mul(f[i], f[i-1]);
	cout << f[n];
	return 0;
}

[BJOI2019]删数
发现顺序不影响,我们在脑中给原序列排一个序
手玩一波 { 1 , 2 , 2 , 3 , 3 , 3 } \{1,2,2,3,3,3\} {1,2,2,3,3,3},显然最优方案是 { 3 , 3 , 3 , 6 , 6 , 6 } \{3,3,3,6,6,6\} {3,3,3,6,6,6}
发现一个巧妙的性质,如果我们在 6 这个位置放一个高为 3 的柱子,在 3 这个位置放一个 高为 3 的柱子
那么向左推到刚好覆盖原序列
那么我们模拟一下把原序列推倒的情况,位置1覆盖了 3 次,位置 2 覆盖了 2 次,而 4,5,6 都没有覆盖
显然需要把重复的 k k k 个覆盖挪到空格上,发现铁定构造地出来
另外,可以很容易的发现,这种构造方法恰是方案数的下界
于是问题转换为求 0 的个数,线段树维护最小值即最小值个数即可
一个单点修改把变动的区间修改即可,整体 + 1 有些头疼
考虑整体+1 有什么变化
{ 1 , 2 , 2 , 3 , 3 , 3 } − − − { 2 , 3 , 3 , 4 , 4 , 4 } \{1,2,2,3,3,3\}--- \{2,3,3,4,4,4\} {1,2,2,3,3,3}{2,3,3,4,4,4}
覆盖区间的变化
[ 1 , 1 ] , [ 1 , 2 ] , [ 1 , 3 ] − − − [ 2 , 2 ] , [ 2 , 3 ] , [ 2 , 4 ] [1,1],[1,2],[1,3]---[2,2],[2,3],[2,4] [1,1],[1,2],[1,3][2,2],[2,3],[2,4],全部挪了一位
现在查询 [ 1 , 6 ] [1,6] [1,6] 的答案相当于在原来查询 [ 0 , 5 ] [0,5] [0,5] 的答案,那么记一个挪的位就可以了

#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch=='-') f = -1; } 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
cs int N = 150050, M = N * 3;
cs int up = 450005;
int n, m, a[N], len;
int c[M]; // 出现次数  
namespace seg{
	int mi[M << 2], ct[M << 2], tg[M << 2];
	#define mid ((l+r)>>1)
	void pushup(int x){
		if(mi[x<<1] < mi[x<<1|1]) mi[x] = mi[x<<1], ct[x] = ct[x<<1];
		if(mi[x<<1] == mi[x<<1|1]) mi[x] = mi[x<<1], ct[x] = ct[x<<1] + ct[x<<1|1];
		if(mi[x<<1] > mi[x<<1|1]) mi[x] = mi[x<<1|1], ct[x] = ct[x<<1|1];
	}
	void build(int x, int l, int r){
		if(l == r){ ct[x] = 1; return; }
		build(x<<1, l, mid); build(x<<1|1, mid+1, r);
		pushup(x);
	}
	void pushnow(int x, int v){ mi[x] += v; tg[x] += v; }
	void pushdown(int x){
		if(tg[x]) pushnow(x<<1, tg[x]), pushnow(x<<1|1, tg[x]), tg[x] = 0;
	}
	void modify(int x, int l, int r, int L, int R, int v){
		if(L<=l && r<=R){ pushnow(x, v); return; } 
		pushdown(x);
		if(L<=mid) modify(x<<1, l, mid, L, R, v);
		if(R>mid) modify(x<<1|1, mid+1, r, L, R, v);
		pushup(x);
	}
	int query(int x, int l, int r, int L, int R){
		if(L<=l && r<=R){
			return mi[x] == 0 ? ct[x] : 0;
		}
		pushdown(x); int ans = 0;
		if(L<=mid) ans += query(x<<1, l, mid, L, R);
		if(R>mid) ans += query(x<<1|1, mid+1, r, L, R);
		return ans;
	}
}
void modify(int x, int v){
	int k = x - c[x] + 1 - (v > 0);
	seg::modify(1, 1, up, k, k, v);
	c[x] += v; 
}
int main(){
	n = read(), m = read();
	len = 150001; seg::build(1, 1, up);
	for(int i = 1; i <= n; i++){
		a[i] = read(); a[i] += len; 
		modify(a[i], 1);
	}
	while(m--){
		int p = read(), x = read();
		if(p == 0){
			if(x > 0){
				int pos = len + n;
				if(c[pos]) seg::modify(1, 1, up, pos - c[pos] + 1, pos, -1);
				--len;
			}
			else{
				++len;
				int pos = len + n; 
				if(c[pos]) seg::modify(1, 1, up, pos - c[pos] + 1, pos, 1);
			}
		}
		else{
			if(a[p] <= len + n) modify(a[p], -1);
			else --c[a[p]];
			a[p] = len + x;
			if(a[p] <= len + n) modify(a[p], 1);
			else ++c[a[p]];
		} cout << seg::query(1, 1, up, len + 1, len + n) << '\n';
	} return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FSYo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值