A
期望具有线性性,可以将第一次选取的数为为
i
i
i 的期望算出来,再把他们相加除以
n
n
n 就是答案,问题转化为 对于第一次选取的数
i
i
i 的期望长度怎么算,
对于一个
k
k
k ,设
f
[
i
]
f[i]
f[i] 是以
i
i
i 开始的期望长度,我们手模几个样例,发现如果选出来的数
i
≥
k
i\geq k
i≥k ,
f
[
i
]
=
1
f[i]=1
f[i]=1 , 对于
i
<
k
i<k
i<k ,
f
[
i
]
=
1
+
1
n
−
i
∑
j
=
i
+
1
n
f
[
j
]
f[i] = 1+\frac{1}{n-i}\sum_{j=i+1}^{n} f[j]
f[i]=1+n−i1∑j=i+1nf[j] ,如何理解相当于从
i
i
i 开始已经长度为 1 了,我只能选择比
i
i
i 大的数,对于
[
i
+
1
,
n
]
[i+1,n]
[i+1,n] 每个数我有
1
n
−
i
\frac{1}{n-i}
n−i1 的概率选中它。因为期望具有线性性,所以求和相加除以平均数+1就是以
i
i
i 开始的期望长度,但是这还不足以通过本题,如果每次跑一遍递推就是
n
2
n^2
n2 的复杂度,发现还可以优化,发现
d
p
dp
dp 式子好像是一个后缀和的形式,那么可以维护后缀和
s
u
m
sum
sum ,那么每次的递推不就变成了
f
[
i
]
=
1
+
1
n
−
i
⋅
s
u
m
f[i] = 1+\frac{1}{n-i}\cdot sum
f[i]=1+n−i1⋅sum,
s
u
m
=
s
u
m
+
f
[
i
]
=
1
+
n
−
i
+
1
n
−
i
s
u
m
sum = sum+f[i]=1+\frac{n-i+1}{n-i}sum
sum=sum+f[i]=1+n−in−i+1sum,我们要求的
f
[
1
]
f[1]
f[1] 已经被包含在最后的
s
u
m
sum
sum 里面,那么就不需要
f
f
f 的递推了,只需要维护
s
u
m
sum
sum 即可,
s
u
m
sum
sum 发现是一次函数复合.
设
f
=
a
x
+
b
,
g
=
c
x
+
d
f = ax+b,g = cx+d
f=ax+b,g=cx+d,
(
f
∘
g
)
(
x
)
=
f
(
g
(
x
)
)
=
a
(
c
x
+
d
)
+
b
(f\circ g)(x) = f(g(x)) = a(cx+d)+b
(f∘g)(x)=f(g(x))=a(cx+d)+b,写成映射形式就是
(
a
,
b
)
∘
(
c
,
d
)
↦
(
a
c
,
a
d
+
b
)
(a,b) \circ (c,d)\mapsto (ac,ad+b)
(a,b)∘(c,d)↦(ac,ad+b) ,那么我们只要根据这个维护关于
s
u
m
sum
sum 一次函数的嵌套即可
O
(
1
)
O(1)
O(1)求解,需要注意的是维护的嵌套顺序不对,需要求解上面映射的逆映射,这题卡空间,不清楚是否可以通过,不过可以提一嘴逆映射怎么求。
复合类似于于左乘矩阵,求逆也是类似的。具体的,现有一次函数复合
F
(
x
)
=
f
1
(
f
2
(
f
3
(
x
)
)
)
F(x)=f_1(f_2(f_3(x)))
F(x)=f1(f2(f3(x))) ,现在我想得到
f
1
(
x
)
f_1(x)
f1(x) ,那么我可以
F
∘
g
(
x
)
=
f
1
(
f
2
(
f
3
(
g
(
x
)
)
)
)
F\circ g(x)= f_1(f_2(f_3(g(x))))
F∘g(x)=f1(f2(f3(g(x)))) , 其中
g
(
x
)
=
f
3
−
1
(
f
2
−
1
(
x
)
)
f
3
−
1
是
f
3
的逆映射
g(x) = f_3^{-1} (f_2^{-1}(x))\quad f_3^{-1} 是 f_3的逆映射
g(x)=f3−1(f2−1(x))f3−1是f3的逆映射
该题还有一个细节,因为
n
n
n 很大,需要线性求逆元
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
constexpr int maxn = 1e7+10;
constexpr int mod = 998244353;
int n,inv[maxn];
array<int,2> pre[maxn];
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n;
inv[1] = 1;
for(int i = 2; i <= n; i++){
inv[i] = 1LL*(mod-mod/i)*inv[mod % i]%mod;
}
pre[1]={1LL*n*inv[n-1]%mod,1};
for(int i = 2;i<n;++i){
pre[i][0] = 1LL*pre[i-1][0]*(n-i+1LL)%mod*inv[n-i]%mod;
pre[i][1] = (1LL*pre[i-1][0]+pre[i-1][1])%mod;
}
i64 ans = n;
i64 A,B;
for(int k = n;k>1;--k){
A = pre[k-1][0];
B = pre[k-1][1];
//cout<<k<<" "<<A<<" "<<B<<"\n";
(ans+=(A*(n-k+1LL)+B))%=mod;
}
(ans*=inv[n])%=mod;
(ans*=inv[n])%=mod;
cout<<ans<<"\n";
return 0;
}
D
注意题目描述,时间可以是实数。首先可以观察到关键时间节点就 2 ∗ n 2*n 2∗n 个,意味着有线段覆盖到 x x x 或者没有交点,那么我们可以对于每个时间节点排序,从上一个时间节点到当前节点的虫子我们可以求出,用 s e t set set 维护虫子长度,对关键节点的 m i n 虫子长度 min虫子长度 min虫子长度 取 m a x max max 即可
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define int long long
constexpr int maxn = 1e5+10;
struct ty{
int l,r,v,len;
ty()=default;
ty(int l,int r,int v,int len):l(l),r(r),v(v),len(len){}
}a[maxn];
struct node{
array<int,2> t;
int id,op;
node(array<int,2> t,int id,int op):t(t),id(id),op(op){}
bool operator<(const node& x)const{
if(t[0]*x.t[1]!=t[1]*x.t[0]) return t[0]*x.t[1]<t[1]*x.t[0];
return op>x.op;
}
};
int n,x;
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>x;
vector<node> event;
for(int i = 1;i<=n;++i){
int l,r,v;
cin>>l>>r>>v;
a[i]=ty(l,r,v,r-l);
array<int,2> time={0,1};
if(r<x){
time = {x-r,v};
event.push_back(node(time,i,1));
time = {x-l,v};
event.push_back(node(time,i,0));//删除前一刻
}else{
if(l<=x){
event.push_back(node(time,i,1));//0时刻就在
time = {x-l,v};
event.push_back(node(time,i,0));//删除前一刻
}
}
}
sort(event.begin(),event.end());
set<array<int,2>> se;
int ans = 0;
vector<array<int,2>> del;
for(int l = 0,r = 0;l<(event.size());){
r = l;
while(!del.empty()){
if(!se.empty()) se.erase(del.back());
del.pop_back();
}
if(!se.empty()) ans = max(ans,(*se.begin())[0]);
while(r<event.size()&&event[l].t[0]*event[r].t[1]==event[l].t[1]*event[r].t[0]){
int len = a[event[r].id].len;
if(event[r].op==1) se.insert({len,event[r].id});
else del.push_back({len,event[r].id});
++r;
}
if(!se.empty()) ans = max(ans,(*se.begin())[0]);
l = r;
}
cout<<ans<<"\n";
return 0;
}
H
给定 哈希模数和底数 问求哈希冲突的概率 ,答案显然是 哈希值一样的对数减去 真正一样的对数,真正一样的对数我们可以自己写一个hash,形成双hash ,双指针求解一下即可
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define int long long
constexpr int maxn = 3e3+10;
constexpr int B = 133;
constexpr i64 mod = 10000000000000061;
int n,p,m;
i64 h[maxn],base[maxn],s[maxn];
int get(int l,int r){
return ((i128)h[r]-(i128)h[l-1]*base[r-l+1]%mod+mod)%mod;
}
vector<int> mp;
vector<int> v;
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>p>>m;
base[0]=1;
for(int i = 1;i<=n;++i){
cin>>s[i];
base[i] = base[i-1]*B%mod;
h[i] = (1LL*h[i-1]*B%mod+s[i])%mod;
}
for(int l =1;l<=n;++l){
int a=0,b_p=1;
for(int r = l;r<=n;++r){
a = (1LL*a+1LL*s[r]*b_p%m)%m;
b_p = 1LL*b_p*p%m;
int b = get(l,r);
mp.emplace_back(a);
v.emplace_back(b);
}
}
i64 ans = 0;
sort(mp.begin(),mp.end());
for(int i = 0;i<mp.size();){
int j = i;
while(j+1<mp.size()&&mp[i]==mp[j+1]) ++j;
ans+=(j-i+1)*(j-i);
i = j+1;
}
sort(v.begin(),v.end());
for(int i = 0;i<v.size();){
int j = i;
while(j+1<v.size()&&v[i]==v[j+1]) ++j;
ans-=(j-i+1)*(j-i);
i = j+1;
}
cout<<ans<<"\n";
return 0;
}
J
去年写的已经忘记了,看个码
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
const int mod = 998244353;
int n,m;
int qp(int a,int b){
int res = 1;
while(b){
if(b&1) res = (res*a)%mod;
b>>=1;
a = (a*a)%mod;
}
return res;
}
signed main(){
ios;
cin>>n>>m;
vector<int> x(n),y(m),px(n),py(m);
for(int i =0;i<n;++i){
cin>>x[i];
px[i] = x[i]+i*20LL;
}
for(int i =0;i<m;++i){
cin>>y[i];
py[i] = y[i]+i*20LL;
}
int j=0;
i64 ans = 0;
for(int k = 0;k<n;++k){
while(py[j]<px[k]&&j<m){
j++;
}
ans = (ans+qp(2,m-j)*qp(2,n-k-1))%mod;
}
cout<<ans<<"\n";
return 0;
}
K
x + 0.5 x+0.5 x+0.5 不好处理我们把他平移到 x x x 处,对于伤害我们把它移到点上面去,题目中给的例子[3,4]和[4,5] 我们看成 hurt[3,4,5]={1,2,1} 这样方便计算,类似于 D D D 的处理方法,发现一段区间的长度是固定的 这样最多分成 2 ∗ n 2*n 2∗n 个区间,那么我们对数轴进行差分。如何精准知道死亡时间呢,我们可以二分线段,看他是在哪死的,就可以了,不过有些细节,出现的那段的伤害和最后死亡的那段不一定吃满的,需要精确计算一下,其余只要前缀和计算一下即可
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
vector<array<int,2>> v;
vector<i64> hurt,pre,duan_hurt;
vector<array<i64,2>> ani;
vector<array<int,2>> duan;
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
int n,m;
cin>>n>>m;
for(int i = 1;i<=n;++i){
int l,r,w;
cin>>l>>r>>w;
v.push_back({l,w});
v.push_back({r+1,-w});
}
for(int i = 1;i<=m;++i){
i64 x,h;
cin>>x>>h;
ani.push_back({x,h});
}
sort(v.begin(),v.end());
i64 now =0,lst =0;
for(int i = 0;i<v.size();){
duan_hurt.emplace_back(now);
hurt.emplace_back((v[i][0]-lst)*now);
duan.push_back({lst,v[i][0]-1});
int j = i;
while(j<v.size()&&v[j][0]==v[i][0]){
now+=v[j][1];
++j;
}
lst = v[i][0];
i = j;
}
i64 sum = 0;
for(auto &i:hurt){
sum+=i;
pre.emplace_back(sum);
}
vector<int> ans;
for(auto &[x,h]:ani){
auto p = lower_bound(duan.begin(),duan.end(),array<int,2>{x+1,x})-duan.begin()-1;//第一段
i64 first_hurt = (duan[p][1]-x)*duan_hurt[p];
if(first_hurt>=h){
ans.emplace_back((h+duan_hurt[p]-1)/duan_hurt[p]);
continue;
}
if(first_hurt+pre.back()-pre[p]<h){
ans.emplace_back(-1);
continue;
}
int l = p+1, r = pre.size()-1;
while(l<=r){
int mid =(l+r)>>1;
if(pre[mid]-pre[p]+first_hurt>=h) r = mid-1;
else l = mid+1;
}
//pre[r]-pre[p]+first<h
i64 rem = h-first_hurt-(pre[r]-pre[p]);
i64 seconds = (rem+duan_hurt[l]-1)/duan_hurt[l];
if(seconds*duan_hurt[l]>hurt[l]) ans.emplace_back(-1);
else ans.emplace_back(seconds+duan[r][1]-x);
}
for(auto &i:ans) cout<<i<<"\n";
return 0;
}
M
观察发现,层与层之间没有关系,手模样例发现,肯定是优先在我自己的子树内匹配掉最后,否则,两个节点会至少多走长度4,比较暴力的想法就是dfs,每次返回未匹配节点的深度和节点个数,在我当前子树内尝试将未匹配节点匹配,这样复杂度最坏 n 2 n^2 n2 怎么优化呢,我们发现每次最多未匹配的节点最多一个,似乎暗示着那我们是不是只要向上传递深度了,合并的过程可以通过启发式合并,我合并需要找到子节点 y 2 y_2 y2 是否含有子节点 y 1 y_1 y1 的深度,比较简单的数据结构就是set,那这样的时间复杂度是 n l o g 2 nlog^2 nlog2 ,足以通过本题
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
constexpr int maxn = 1e5+10;
vector<int> g[maxn];
set<int> se[maxn];
int n,dep[maxn];
i64 ans =0;
void dfs(int x,int fa){
dep[x] = dep[fa]+1;
se[x].insert(dep[x]);
if(g[x].size()==1&&fa!=0) return;
for(auto &y:g[x]){
if(y==fa) continue;
dfs(y,x);
}
for(auto y:g[x]){
if(y==fa) continue;
if(se[x].size()<se[y].size()) se[x].swap(se[y]);
for(auto z:se[y]){
auto p = se[x].lower_bound(z);
if(p==se[x].end()||*p!=z) se[x].insert(z);
else{
se[x].erase(p);
ans+=2LL*abs(z-dep[x]);
}
}
}
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n;
for(int i = 1;i<n;++i){
int x,y;
cin>>x>>y;
g[x].emplace_back(y);
g[y].emplace_back(x);
}
dfs(1,0);
cout<<ans<<"\n";
return 0;
}