2.9~2.13 做题笔记
我就把这两天写的乱七八糟的小题写写
P7560 [JOISC 2021 Day1] フードコート
题目名直译过来是食品店。
考虑这些宽宏的部分分。
先是只有进人和询问的。
显然对于每一个询问都是要找到一个时间点使得此刻之后的人数要大于等于询问人数,这个时候加进来的团体就是答案。如果这个时间点比询问时间点大的话,就说明这个甜品被员工吃了。这个可以整体二分,也可以把询问挂在线段树上,线段树的下标是食品店的标号,然后顺次加入操作一,如果加入之后当前点的值比询问大了,就更新答案。
考虑怎么处理走人的情况。询问的 \(B\) 是在刨除掉走的人之后已经来过的人数的下限,也就是说在走掉这么多人后应该达到的人数。假设这些人没有走,把询问加上走的人的数量,而修改代表的还是只加进人。这样就排除了走人的干扰。
但是现在的问题是怎样计算询问的确切数字。因为走的人不保证小于等于当前队伍里存在的人,所以不能直接计算。其实就是计算总进来过的人数,用线段树维护每个位置的当前的人数,相减就能知道走了多少人。加上询问的 \(B\) 之后就知道有多少人来过了。
代码一般好写吧。
/*
/> フ
| _ _|
/`ミ _x 彡
/ |
/ ヽ /
/ ̄| | | |
| ( ̄ヽ__ヽ_)_)
\二つ
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=350009;
const ll inf=0x3f3f3f3f3f3f3f3fll;
int n,m,Q;
namespace BIT
{
ll c[maxn];
void add(int x,ll col){for(;x<=n;x+=(x&(-x))) c[x]+=col;}
ll query(int x){ll ret=0;for(;x;x-=(x&(-x))) ret+=c[x]; return ret;}
}
#define lson(p) ((p)<<1)
#define rson(p) ((p)<<1|1)
namespace SGT
{
ll c[maxn*4],tag[maxn*4];
void chg(int p,ll val,ll mx)
{
tag[p]+=val;
c[p]=max(c[p]+val,mx);
}
void pushdown(int p)
{
chg(lson(p),tag[p],c[p]); chg(rson(p),tag[p],c[p]);
tag[p]=0; c[p]=0;
}
void modify_add(int l,int r,int x,int y,ll v,int p)
{
if(x<=l&&y>=r)
{
chg(p,v,0);
return;
}
if(tag[p]||c[p]) pushdown(p);
int mid=(l+r)>>1;
if(x<=mid) modify_add(l,mid,x,y,v,lson(p));
if(y>mid) modify_add(mid+1,r,x,y,v,rson(p));
}
ll query(int l,int r,int pos,int p)
{
if(l==r) return max(c[p],tag[p]);
int mid=(l+r)>>1;
if(tag[p]||c[p]) pushdown(p);
if(pos<=mid) return query(l,mid,pos,lson(p));
else return query(mid+1,r,pos,rson(p));
}
}
ll ans[maxn];
namespace seg
{
ll tag[maxn*4],mn[maxn*4];
vector<pair<ll,ll>> q[maxn];
int st[maxn];
void build(int l,int r,int p)
{
if(l==r)
{
if(q[l].size()) sort(q[l].begin(),q[l].end()),mn[p]=q[l][0].first;
else mn[p]=inf;
return;
}
int mid=(l+r)>>1;
build(l,mid,lson(p)); build(mid+1,r,rson(p));
mn[p]=min(mn[lson(p)],mn[rson(p)]);
}
void pushdown(int p)
{
tag[lson(p)]+=tag[p]; tag[rson(p)]+=tag[p];
mn[lson(p)]+=tag[p]; mn[rson(p)]+=tag[p];
tag[p]=0;
}
void modify(int l,int r,int x,int y,ll v,int p)
{
if(x<=l&&y>=r) return mn[p]+=v,tag[p]+=v,void();
int mid=(l+r)>>1;
if(tag[p]) pushdown(p);
if(x<=mid) modify(l,mid,x,y,v,lson(p));
if(y>mid) modify(mid+1,r,x,y,v,rson(p));
mn[p]=min(mn[lson(p)],mn[rson(p)]);
}
void solve(int l,int r,ll val,int tim,int p)
{
if(mn[p]>0) return;
if(l==r)
{
while(st[l]<(int)q[l].size()&&q[l][st[l]].first+tag[p]<=0)
{
ans[q[l][st[l]].second]=val;
st[l]++;
}
if(st[l]==(int)q[l].size()) mn[p]=inf;
else mn[p]=q[l][st[l]].first+tag[p];
return;
}
int mid=(l+r)>>1;
if(tag[p])
pushdown(p);
solve(l,mid,val,tim,lson(p)); solve(mid+1,r,val,tim,rson(p));
mn[p]=min(mn[lson(p)],mn[rson(p)]);
}
}
ll L[maxn],R[maxn],C[maxn],K[maxn],A[maxn],B[maxn],opt[maxn],idx[maxn];
int main()
{
//freopen("p.in","r",stdin);
//freopen("p.out","w",stdout);
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=Q;i++)
{
scanf("%lld",&opt[i]);
if(opt[i]==1)
{
scanf("%lld%lld%lld%lld",&L[i],&R[i],&C[i],&K[i]);
BIT::add(L[i],K[i]); BIT::add(R[i]+1,-K[i]);
SGT::modify_add(1,n,L[i],R[i],K[i],1);
}
else if(opt[i]==2)
{
scanf("%lld%lld%lld",&L[i],&R[i],&K[i]);
SGT::modify_add(1,n,L[i],R[i],-K[i],1);
}
else
{
scanf("%lld%lld",&A[i],&B[i]);
ll cur=SGT::query(1,n,A[i],1);
if(cur>=B[i])seg::q[A[i]].push_back(make_pair(B[i]+BIT::query(A[i])-cur,i));
}
}
seg::build(1,n,1);
for(int i=1;i<=Q;i++)
{
if(opt[i]==1)
{
seg::modify(1,n,L[i],R[i],-K[i],1);
seg::solve(1,n,C[i],i,1);
}
}
for(int i=1;i<=Q;i++)
{
if(opt[i]==3)
{
printf("%lld\n",ans[i]);
}
}
}
[ABC271F] XOR on Grid Path
几乎是明示折半搜索了,但是我因为算错复杂度沉迷暴力不可自拔。
考虑从左下角和右上角分别搜,在对角线处统计答案即可。
[ABC271G] Access Counter
一眼矩阵加速喵。
他问的是第 \(n\) 次,那么我们考虑从一次转移到另外一次的概率。
假设上一次是 \(i\) 点登录的,下一次是在 \(j\) 点登录的,中间没有人碰过电脑。那么概率就是 \(p_j\prod_{k=i+1}^{j-1} (1-p_i)+Q\times p_j\prod_{k=i+1}^{j-1} (1-p_i)+Q^2p_j\prod_{k=i+1}^{j-1} (1-p_i)+\dots\)
\(p_i\) 表示第 \(i\) 时刻登录的概率,\(Q\) 表示整整一天每人动电脑的概率。
上个式子就是 \(p_j\prod_{k=i+1}^{j-1} (1-p_k)(1+Q+Q^2+\dots)\),后面的式子中 \(Q\) 为 \(0\sim 1\) 之间的数,显然是收敛的,可以用等比数列求和公式化成 \(\frac{1}{1-Q}\)
这样我们就能列出转移矩阵了,\(a_{i,j}=\frac{1}{1-Q}p_j\prod_{k=i+1}^{j-1} (1-p_k)\)
然后直接上矩阵加速就行。
可以认为第 \(0\) 次登录为前晚 \(23\) 点。然后就在青木对应的点下面的概率加起来就行。
#6169. 相似序列
首先我自己能想到应该是个哈希。因为之和每种数字的种类数有关。所以把 \(base\) 的 \(k\) 次幂乘到每种数字的数量上。
然后就是考虑如何判定是否差一位。可以分析出来的是必须是有两种数字不同,他们两个分别差正负一,并且两种数字中间两个串都没有其他数字了。
然后考虑怎么做,可以想到的是用主席树来维护子串的哈希,主席树上二分找到左边第一个不同的数,右边第一个不同的数,中间看一下有没有数字,再记录一下差值,就行了。都是简单是操作。
std 有个看不懂的维护方式。
这也不难啊,我应该能切的才对啊
CF961G Partitions
我在好久之前的学长的博客里翻到这道题,我觉得我肯定能推出来,结果还是得靠人家的博客
对每个数拆贡献,得到
\(\displaystyle \sum_{i=1}^n w_i \sum_{j=1}^{n}j\dbinom{n-1}{j-1}\begin{Bmatrix}n-j\\k-1\end{Bmatrix}\)
发现第一个求和和第二个关系不大,所以只看第二个怎么求。其实我想硬上个斯特林数求列
\(\displaystyle \sum_{j=1}^n j\dbinom {n-1}{j}\sum_{l=0}^{k-1} \frac {(-1)^l(k-1-l)^{n-1-j}}{l!(k-1-l)!}\)
\(\displaystyle \sum_{l=0}^{k-1} \frac {(-1)^l}{l!(k-1-l)!}\sum _{j=1} j\dbinom{n-1}{j-1}(k-1-l)^{n-1-j}\)
先只看第二个求和
\(\displaystyle \sum_{j=1}^n \dbinom{n-1}{j-1}(k-1-l)^{n-1-j}+\sum_{j=1}^n (j-1)\dbinom{n-1}{j-1}(k-1-l)^{n-1-j}\)
\(\displaystyle \sum_{j=1}^{n}\dbinom{n-1}{j-1}(k-1-l)^{n-1-j}+(n-1) \sum_{j=1}^{n} \dbinom{n-2}{j} (k-1-l)^{n-1-j}\)
(这里是提出来个 \(n-2\) 和 \(j-1\),\(j-1\) 乘没了)
\((k-l)^{n-1}+(n-1)(k-l)^{n-2}\)
然后整个的和就能 \(O(n)\) 算了。

浙公网安备 33010602011771号