2021牛客暑期多校训练营4

2021牛客暑期多校训练营4

B:Sample_Game

解释

期望DP, 逆推

考虑 d p 1 ( i ) dp1(i) dp1(i)​ 表示第一个以i开始时所得到的期望长度值, d p 2 ( i ) dp2(i) dp2(i)​ 表示第一个以i开始时所得到的期望长度的平方,即答案。

首先 d p 2 ( i + 1 ) = ( d p 1 ( i ) + 1 ) 2 = d p 2 ( i ) 2 + 2 d p 1 ( i ) + 1 dp2(i+1) = (dp1(i)+1)^2= dp2(i)^2 + 2dp1(i) + 1 dp2(i+1)=(dp1(i)+1)2=dp2(i)2+2dp1(i)+1​​​​​​ , 可以看成是 ( c i + 1 ) 2 = ( c i + 1 ) 2 = ( c i ) 2 + 2 × c i + 1 (c_{i+1})^2 = (c_i+1)^2 = (c_i)^2+2\times c_i + 1 (ci+1)2=(ci+1)2=(ci)2+2×ci+1

那么有转移方程
d p 1 ( i ) = ∑ j = i n p i × ( d p 1 ( j ) + 1 ) + ∑ j = 1 i − 1 p j × 2 dp1(i) = \sum_{j=i}^{n}p_i\times (dp1(j) + 1) + \sum_{j=1}^{i-1}p_j\times2 dp1(i)=j=inpi×(dp1(j)+1)+j=1i1pj×2

d p 2 ( i ) = ∑ j = i n p i × ( d p 2 ( j ) 2 + 2 d p 1 ( j ) + 1 ) + ∑ j = 1 i − 1 p j × ( 2 ) 2 dp2(i) = \sum_{j=i}^{n}p_i\times (dp2(j)^2+2dp1(j)+1) + \sum_{j=1}^{i-1}p_j\times (2)^2 dp2(i)=j=inpi×(dp2(j)2+2dp1(j)+1)+j=1i1pj×(2)2

对于两个数,前半部分都表示,当下一步还可以走,那么得到的期望 (长度/长度平方) ,和当下一步结束时,得到的期望 (长度/长度平方),

基本形式都是 (概率 x (下一步的期望 (长度/长度平方) + 当前元素的贡献) )

后半部分解释,当前元素的贡献为1,下一个不可走的贡献也为1。即为2。不用dp因为定义和此处矛盾

然后将公式化简 dp(i) 全部在左边,逆推公式就出来了。有些地方可以维护前缀和,也可以不维护,数据范围小。

最后答案就是 ∑ i = 1 n d p 2 ( i ) × p ( i ) \sum_{i=1}^{n} dp2(i)\times p(i) i=1ndp2(i)×p(i)

参考博客中别人写的最后答案不是这个,是因为定于有些出入,默认是处理到当前元素但并未将当前元素加入集合。

B-Sample Game 2021牛客暑期多校训练营4【概率DP】_tcy今天长胖了吗的博客-CSDN博客

【训练题43:概率dp】Sample Game | 2021牛客暑期多校训练营4_溢流眼泪的博客-CSDN博客

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e2 + 10;
const int mod = 998244353;
#define int long long

int dp1[N],dp2[N],p[N];
int sum1[N],sum2[N];

int qmi(int a,int b){
    int res = 1;
    while(b){
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

signed main(){
	IOS
    int n,w = 0; cin>>n;
    for(int i=1;i<=n;i++) cin>>p[i],w += p[i];
    int inv = qmi(w,mod-2);
    for(int i=1;i<=n;i++) p[i] = p[i]*inv%mod,sum1[i] = (sum1[i-1] + p[i]) % mod;
    for(int i=n;i>=1;i--){
        int up = 0,down = qmi((1-p[i]+mod)%mod,mod-2);
        for(int j=i+1;j<=n;j++) up = (up + p[j]*dp1[j]%mod)%mod;
        up = (up + (sum1[n]+sum1[i-1])%mod) % mod;
        dp1[i] = up * down % mod;
    }
    int ans = 0;
    for(int i=1;i<=n;i++) sum2[i] = (sum2[i-1] + p[i]*dp1[i]%mod) % mod;
    for(int i=n;i>=1;i--){
        int up = 0,down = qmi((1-p[i]+mod)%mod,mod-2);
        for(int j=i+1;j<=n;j++) up = (up + p[j]*dp2[j]%mod)%mod;
        up = (up + 2*(sum2[n]-sum2[i-1]+mod)%mod)%mod;
        up = (up + (sum1[n]+3*sum1[i-1]%mod)%mod)%mod;
        dp2[i] = up * down % mod;
        ans = (ans + dp2[i]*p[i]%mod) % mod;
    }
    cout<<ans<<endl;
    return 0;
}

C:LCS

解释

先把三个值最小的填上,后面再两两填。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;

string sa = "",sb = "",sc = "";

signed main(){
	IOS
    int a,b,c,n; cin>>a>>b>>c>>n;
	int mi = min(min(a,b),c);
	bool plas = true;
    for(int i=0;i<mi;i++) sa += 'a',sb += 'a',sc += 'a';
    if(a == mi){
    	for(int i=0;i<b-mi;i++) sb += 'b',sc += 'b',sa += 'x'; 
    	if(c-mi > (n - (int)sc.size())) plas = false;
    	else{
    		for(int i=0;i<c-mi;i++) sb += 'y',sc += 'c',sa += 'c';
    		while((int)sa.size() < n) sa += 'd';
    		while((int)sb.size() < n) sb += 'e';
    		while((int)sc.size() < n) sc += 'f';
		}
	}else if(b == mi){
		for(int i=0;i<a-mi;i++) sb += 'b',sa += 'b',sc += 'x'; 
    	if(c-mi > (n - (int)sc.size())) plas = false;
    	else{
    		for(int i=0;i<c-mi;i++) sb += 'y',sc += 'c',sa += 'c';
    		while((int)sa.size() < n) sa += 'd';
    		while((int)sb.size() < n) sb += 'e';
    		while((int)sc.size() < n) sc += 'f';
		}
	}else{
		for(int i=0;i<b-mi;i++) sb += 'b',sc += 'b',sa += 'x'; 
    	if(a-mi > (n - (int)sa.size())) plas = false;
    	else{
    		for(int i=0;i<a-mi;i++) sc += 'y',sb += 'c',sa += 'c';
    		while((int)sa.size() < n) sa += 'd';
    		while((int)sb.size() < n) sb += 'e';
    		while((int)sc.size() < n) sc += 'f';
		}
	}
    if(!plas) cout<<"NO"<<endl;
    else cout<<sa<<'\n'<<sb<<'\n'<<sc<<endl;
    
	return 0;
}

E:Tree_Xor

解释

很容易发现可以枚举一个点范围中的数,就能确定其它所有点的值,判断是否满足n-1个不等式,就能确定方案是否合法。但是枚举一个点的所有情况的代价比较大。

上述暴力的思想可以转换成每个点合法区间的交集,而合法区间会异或上固定点到当前点的异或前缀,使得原本连续的区间,变成不连续。有一种做法,看0 ~ (1 << 30)-1的线段树。这个线段树有很多规律和性质。参考下面博客

参考博客 参考博客 参考博客

这样能快速找到区间异或上一个数之后,被分开的区间。之后进行区间交集或者用线段树动态开点维护不合法的区间。

代码

直接将合法区间存下,再求n个区间交。

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 2e5 + 10,M = (1<<30)-1;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;

int ne[N],e[N],w[N],h[N],idx=1;
int val[N],L[N],R[N],n;
vector<pii> ve;

#define l(x) t[x].l
#define r(x) t[x].r

struct SegmentTree{
	int l,r;
}t[N<<5];
int id = 0,rt = 0;

void add(int a,int b,int c){
	w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++; 
}

void update(int &p,int l,int r,int ql,int qr,int v){
	if(!p) p = ++id;
	if(ql <= l && qr >= r){
		int ansl = l^(v&(~(r-l))); // 理解理解
		int ansr = ansl + r - l;
		ve.push_back(make_pair(ansl,1));
		ve.push_back(make_pair(ansr+1,-1));
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid) update(l(p),l,mid,ql,qr,v);
	if(qr > mid) update(r(p),mid+1,r,ql,qr,v);
}

int cal(){
	int res = 0,cnt = 0;
	sort(ve.begin(),ve.end());
	ve.push_back(make_pair(M+1,-1));
	for(int i=0;i<(int)ve.size()-1;i++){
		cnt += ve[i].second;
		if(cnt == n) res += (ve[i+1].first - ve[i].first);
	}
	return res;
}

void dfs(int u,int fa){
	update(rt,0,M,L[u],R[u],val[u]);
	for(int i=h[u];i;i=ne[i]){
		int j = e[i];
		if(j == fa) continue;
		val[j] = w[i] ^ val[u];
		dfs(j,u);
	}
}

signed main(){
	IOS
    cin>>n;
    for(int i=1;i<=n;i++) cin>>L[i]>>R[i];
    for(int i=1;i<n;i++){
    	int a,b,c; cin>>a>>b>>c;
    	add(a,b,c); add(b,a,c);
	}
    dfs(1,0);
    cout<<cal()<<endl;
	return 0;
}

线段树维护违法区间,最后减去

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 2e5 + 10,M = (1<<30)-1;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;

int ne[N],e[N],w[N],h[N],idx=1;
int val[N],L[N],R[N],n;

#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum

struct SegmentTree{
	int l,r,sum;
}t[N<<5];
int id = 0,rt = 0;

void add(int a,int b,int c){
	w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++; 
}

void update(int &p,int l,int r,int ql,int qr){
	if(!p) p = ++id;
	if(sum(p) == r - l + 1) return ;
	if(ql <= l && qr >= r){
		sum(p) = r - l + 1;
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid) update(l(p),l,mid,ql,qr);
	if(qr > mid) update(r(p),mid+1,r,ql,qr);
	sum(p) = sum(l(p)) + sum(r(p));
}

void slove(int l,int r,int ql,int qr,int v){
	if(ql <= l && qr >= r){
		int ansl = l^(v&(~(r-l))); 
		int ansr = ansl + r - l;
		update(rt,0,M,ansl,ansr);
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid) slove(l,mid,ql,qr,v);
	if(qr > mid) slove(mid+1,r,ql,qr,v);
}

void dfs(int u,int fa){
	if(L[u]) slove(0,M,0,L[u]-1,val[u]);
	if(R[u] < M) slove(0,M,R[u]+1,M,val[u]);
	for(int i=h[u];i;i=ne[i]){
		int j = e[i];
		if(j == fa) continue;
		val[j] = w[i] ^ val[u];
		dfs(j,u);
	}
}

signed main(){
	IOS
    cin>>n;
    for(int i=1;i<=n;i++) cin>>L[i]>>R[i];
    for(int i=1;i<n;i++){
    	int a,b,c; cin>>a>>b>>c;
    	add(a,b,c); add(b,a,c);
	}
    dfs(1,0);
    int ans = M - sum(rt) + 1;
    cout<<ans<<endl;
	return 0;
}

F:Just_a_joke

解释

  1. 第一个操作,边减1

  2. 第二个操作,点减a个,边减a-1个,总共减少2*a-1个

上述两个操作都是对 n + m 上的奇数的减少。所以判断n + m是偶数还是奇数。

代码

int n,m; cin>>n>>m;
for(int i=1,a,b;i<=m;i++) cin>>a>>b;
cout<<(((n+m)&1)?"Alice":"Bob")<<endl;

I:Inverse_Pair

解释

贪心思想,对每一个没有变化的数,将后面的当前的数减1的数加1,变成相等,然后再用树状数组统计逆序对。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;

int a[N],n;
bool vis[N];
map<int,int> mp;

#define lowbit(x) ((x) & (-x))
int c[N];

void change(int x,int v){
	for(int i=x;i<=n;i+=lowbit(i)) c[i] += v; 
}

int ask(int x){
	int res = 0;
	for(int i=x;i;i-=lowbit(i)) res += c[i];
	return res;
}

signed main(){
	IOS
    cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]] = i;
    for(int i=1;i<=n;i++){
    	if(vis[i]) continue;
    	int j = a[i] - 1;
    	if(mp[j] > i){
    		a[mp[j]] = a[i];
    		vis[mp[j]] = true;
		}
		vis[i] = true;
	}
	int ans = 0;
	for(int i=1;i<=n;i++){
		ans += ask(n) - ask(a[i]);
		change(a[i],1);
	}
	cout<<ans<<endl;
	return 0;
}

J:Average

解释

乘起来发现,选择一个子区域,使得平均值最大,即选择一段长度自少为m的连续的a和连续的b,它们的平均值最大。剩下的就是最佳牛围栏的板子。二分加前缀和处理。

代码

#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-8;
const int INF = 0x3f3f3f3f;

int n,m,x,y;
double a[N],b[N];
double suma[N],sumb[N];
double ansa,ansb;

bool check1(double avg,int n,int m) {
    for (int i = 1; i <= n; i++) {
        suma[i] = suma[i - 1] + (a[i] - avg);
    }

    double minv = 0;
    for (int i = 0, j = m; j <= n; j++, i++) {
        minv = min(minv, suma[i]);
        if(suma[j] - minv >= 0) return true;
    }
	return false;
}

bool check2(double avg,int n,int m) {
    for (int i = 1; i <= n; i++) {
        sumb[i] = sumb[i - 1] + (b[i] - avg);
    }

    double minv = 0;
    for (int i = 0, j = m; j <= n; j++, i++) {
        minv = min(minv, sumb[i]);
        if(sumb[j] - minv >= 0) return true;
    }
	return false;
}

signed main(){
	IOS
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++) cin>>b[i];
    double l = 0,r = 1e9;
    while((r - l) > eps){
    	double mid = (l + r)/2;
    	if(check1(mid,n,x)) l = mid;
    	else r = mid;
	}
    ansa = r;
    
    l = 0,r = 1e9;
    while((r - l) > eps){
    	double mid = (l + r)/2;
    	if(check2(mid,m,y)) l = mid;
    	else r = mid;
	}
    ansb = r;
    double ans = ansa + ansb;
    cout<<fixed<<setprecision(7)<<ans<<endl;
    
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值