2025.5.9-2025.5.10做题记录
前言
感觉好忙好累,但不知道都干了什么。
怎么会有人盯着题看但是除了题什么都能想到。
甚至可以脑子里开一把 dwrg 都不去做题。
题目列表
A:Enchanted
Minecraft 背景好评。这种题没强在谁写在线谁……
读完题就能猜到要上个数据结构
操作 3 是单点修改,无需多言。
操作 1 的初看可能会觉得难以处理,但我们在看 \(q\) 的范围是 \(q\le 30\) 考虑直接状压附魔书等级。
然后合成其实就变成了加法,最大等级就是最高位 1 。
操作 2 影响的附魔书等级范围是 \(q\to k\) 取出这些位置的数,然后看末尾多少个连续 1 ,然后暴力计算贡献。(反正 \(q\le 30\))
然后关于操作 4,因为它没有强在,建立版本树直接 DFS 一遍,回溯时撤销操作就行。
总体而言可以拿树状数组维护,就不用写线段树了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lowbit(i) i&-i
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
int n,m,A,p,q;
int a[N],ans[N];
vector<int> vec[N];
struct node{
int op,l,r,k;
}t[N];
int rnd(){
A=(7*A+13)%19260817;
return A;
}
int tr[N];
void update(int x,int k){
for(int i=x;i<=n;i+=lowbit(i)){
tr[i]+=k;
}
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i)){
res+=tr[i];
}
return res;
}
void dfs(int x){
int pre;
if(t[x].op==0){
for(int i=1;i<=n;i++){
update(i,1<<(a[i]-1));
}
}else if(t[x].op==1){
ans[x]=floor(log2(query(t[x].r)-query(t[x].l-1)))+1;
}else if(t[x].op==2){
int res=query(t[x].r)-query(t[x].l-1);
res>>=(t[x].k-1);
ans[x]=0;
int k=1ll<<(t[x].k+1);
while(res&1){
ans[x]+=k;
res>>=1;
k<<=1;
ans[x]%=mod;
}
}else if(t[x].op==3){
int now=t[x].l,k=1ll<<(t[x].k-1);
pre=a[now];
update(now,k-(1<<(a[now]-1)));
a[now]=t[x].k;
}
for(int v:vec[x]){
dfs(v);
}
if(t[x].op==3){
int now=t[x].l;
update(now,(1<<(pre-1))-(1<<(a[now]-1)));
a[now]=pre;
}
}
signed main(){
cin>>n>>m>>A>>p>>q;
for(int i=1;i<=n;i++){
a[i]=rnd()%q+1;
}
memset(ans,-1,sizeof(ans));
for(int i=1;i<=m;i++){
t[i].op=rnd()%p+1;
if(t[i].op==1){
int L=rnd()%n+1,R=rnd()%n+1;
t[i].l=min(L,R);
t[i].r=max(L,R);
}else if(t[i].op==2){
int L=rnd()%n+1,R=rnd()%n+1;
t[i].l=min(L,R);
t[i].r=max(L,R);
t[i].k=rnd()%q+1;
}else if(t[i].op==3){
t[i].l=rnd()%n+1;
t[i].r=t[i].l;
t[i].k=rnd()%q+1;
}else if(t[i].op==4){
t[i].l=rnd()%i;
t[i].r=t[i].l;
}
}
for(int i=1;i<=m;i++){
if(t[i].op==4){
vec[t[i].l].push_back(i);
}else{
vec[i-1].push_back(i);
}
}
dfs(0);
for(int i=1;i<=m;i++){
if(ans[i]!=-1) cout<<ans[i]<<'\n';
}
return 0;
}
B:Bi-ing Lottery Treekets
数据范围和题目基本明示我们是树形 dp 计数,考虑设计状态。不会!
按照往常树形 dp 的思路根本没有一个好用的状态设计,所以我们得换个思路。
定义 \(f_{i,j}\) 表示节点 \(i\) ,从外面接受 \(j\) 个球的方案数。
然后为了不重不漏的计算,我们定义这 \(j\) 个球完全相同,最后用 排列数 去统计方案数。
接下来我们考虑状态转移:
首先预处理出每个节点会落下多少球 \(a_i\),以及每个节点能容纳外面来的多少个球 \(b_i\),以及节点 \(i\) 的子树内有的球数 \(cnt_i\),和子树大小 \(siz_i\)。
状态转移方程看代码吧
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=4e3+10;
const int p=1e9+7;
int n,k;
//f[i][j] 表示节点 i 从外面接受 j 个球的方案数
ll f[N][N];
//a 表示节点内应有多少球,b 表示节点能容下多少球
int a[N],b[N],l[N],r[N];
ll fac[N],inv[N];
ll qpow(ll a,int b){
ll ans=1;
while(b){
if(b&1) ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
ll C(int a,int b){
return fac[a]*inv[a-b]%p*inv[b]%p;
}
ll A(int a,int b){
return fac[a]*inv[a-b]%p;
}
int siz[N],cnt[N];
void dfs(int u,bool flag){
int ls=l[u],rs=r[u];
if(l[u]) dfs(l[u],1);
if(r[u]) dfs(r[u],0);
siz[u]=siz[ls]+siz[rs]+1;
cnt[u]=cnt[ls]+cnt[rs]+a[u];
b[u]=siz[u]-cnt[u];
//给这个节点多少个球
for(int i=0;i<=b[u]&&i<=k-cnt[u];i++){
int t1=flag ? ls : rs;
int t2=flag ? rs : ls;
//给第一个儿子节点多少个球
for(int j=0;j<=b[t1]&&j<=a[u];j++){
int y=min(i,b[t1]-j),F=(i==b[u]);
ll tmp1=f[t1][j+y]*A(j+y,j)%p;
ll tmp2=f[t2][a[u]+i-j-y-F]*A(a[u]+i-j-y,a[u]-j)%p;
f[u][i]=(f[u][i]+1ll*C(a[u],j)*tmp1%p*tmp2%p)%p;
}
}
}
int main(){
#ifdef LOCAL
freopen("D:/Desktop/cpp/data/code.in","r",stdin);
freopen("D:/Desktop/cpp/data/code.out","w",stdout);
#endif
cin>>n>>k;
fac[0]=inv[0]=f[0][0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]*i%p;
inv[i]=qpow(fac[i],p-2)%p;
}
for(int i=1;i<=k;i++){
int x;cin>>x;
a[x]++;
}
for(int i=1;i<=n;i++){
cin>>l[i]>>r[i];
}
dfs(1,0);
cout<<f[1][0];
return 0;
}