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=i∑npi×(dp1(j)+1)+j=1∑i−1pj×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=i∑npi×(dp2(j)2+2dp1(j)+1)+j=1∑i−1pj×(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
-
第二个操作,点减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;
}