2024.11.25 考试总结
100+0+50+0,rk10。
T1
简单树形 \(dp\) 带国王游戏经典贪心,时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,f[N],ts[N],cs[N];
struct kng{int t,sum,ed;};
vector<int>g[N],w[N];
vector<kng>kg[N];
int cmp(kng x,kng y){
return (x.t+x.ed)*y.sum<(y.t+y.ed)*x.sum;
}inline void dfs(int x){
for(int i=0;i<g[x].size();i++){
dfs(g[x][i]),cs[x]+=cs[g[x][i]];
f[x]+=f[g[x][i]],ts[x]+=ts[g[x][i]]+w[x][i];
kg[x].push_back({ts[g[x][i]],cs[g[x][i]],w[x][i]});
}sort(kg[x].begin(),kg[x].end(),cmp);int s=0;
for(auto y:kg[x])
s+=y.ed,f[x]+=s*y.sum,s+=y.t;
}signed main(){
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2,fa,c;i<=n;i++)
cin>>fa>>c,g[fa].push_back(i),w[fa].push_back(c);
for(int i=1;i<=n;i++) cin>>cs[i];
dfs(1),cout<<f[1];
return 0;
}
T2
问题通过 \(dfs\) 序容易转化为区间推平、区间求和和区间回溯。用可持久化 \(treap\) 维护珂朵莉树即可。时间复杂度 \(O(n\log n)\),空间在一片 \(100MB\) 中达到了 \(1.3GB\),大空间选手可喜可贺。线段树做法懒得想了。
#include<bits/stdc++.h>
#define ls(x) pl[x].ls
#define rs(x) pl[x].rs
#define sz(x) pl[x].sz
#define rk(x) pl[x].rk
#define cl(x) pl[x].cl
#define l(x) pl[x].l
#define r(x) pl[x].r
using namespace std;
const int N=7e7+5;
const int M=5e5+5;
struct fhq{
int ls,rs,l,r,sz,rk,cl;
}pl[N];int n,id,rt[M],ans[M];
int mk(int l,int r,int c){
return pl[++id]={0,0,l,r,1,rand(),c},id;
}void push_up(int x){
sz(x)=sz(ls(x))+sz(rs(x))+1;
}void spilt(int x,int v,int &a,int &b){
if(!x) return a=b=0,void();
if(sz(ls(x))<v){
a=++id,pl[a]=pl[x];
spilt(rs(x),v-sz(ls(x))-1,rs(a),b);
push_up(a);
}else{
b=++id,pl[b]=pl[x];
spilt(ls(x),v,a,ls(b));
push_up(b);
}
}int merge(int x,int y){
if(!x&&!y) return 0;
if(!x||!y){
pl[++id]=pl[x+y];
return id;
}if(rk(x)<rk(y)){
int re=x;
rs(re)=merge(rs(re),y);
return push_up(re),re;
}int re=y;
ls(re)=merge(x,ls(re));
return push_up(re),re;
}int find(int x,int v){
if(l(x)<=v&&v<=r(x)) return sz(ls(x))+1;
if(v<l(x)) return find(ls(x),v);
return find(rs(x),v)+sz(ls(x))+1;
}int sum(int x){
if(!x) return 0;
return sum(ls(x))+sum(rs(x))+(1-cl(x))*(r(x)-l(x)+1);
}void assign(int x,int l,int r,int v){
int lx=find(rt[x],l),a,b,c;
spilt(rt[x],lx-1,a,b),spilt(b,1,b,c);
int lc=l(b),rc=r(b),co=cl(b);
if(lc==l) rt[x]=merge(a,merge(b,c));
else rt[x]=merge(a,merge(merge(mk(lc,l-1,co),mk(l,rc,co)),c));
int rx=find(rt[x],r);
spilt(rt[x],rx-1,a,b),spilt(b,1,b,c);
lc=l(b),rc=r(b),co=cl(b);
if(rc==r) rt[x]=merge(a,merge(b,c));
else rt[x]=merge(a,merge(merge(mk(lc,r,co),mk(r+1,rc,co)),c));
lx=find(rt[x],l),rx=find(rt[x],r);
spilt(rt[x],rx,b,c),spilt(b,lx-1,a,b);
rt[x]=merge(merge(a,mk(l,r,v)),c);
ans[x]+=sum(b);
}int dfn[M],low[M],ij,q,vis[M];
vector<int>g[M],chg[M];
void dfs(int x){
dfn[x]=++ij;
for(auto y:g[x]) dfs(y);
low[x]=ij;
}void geta(int x,int fa,int zx){
ans[x]=ans[fa],rt[x]=++id;
pl[id]=pl[rt[fa]];
for(auto i:chg[x])
assign(x,dfn[i],low[i],1);
if(vis[x]&&!zx) assign(x,dfn[x],low[x],1);
for(auto y:g[x]) geta(y,x,vis[x]|zx);
}signed main(){
freopen("reward.in","r",stdin);
freopen("reward.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
srand(time(0)),cin>>n>>q;
for(int i=2,fa;i<=n;i++)
cin>>fa,g[fa].push_back(i);
for(int i=1;i<=q;i++){
int x,y;cin>>x>>y;
if(x==y) continue;
chg[x].push_back(y);
chg[y].push_back(x);
vis[x]=vis[y]=1;
}dfs(1),rt[0]=mk(1,n,0),geta(1,0,0);
for(int i=1;i<=n;i++)
cout<<ans[i]-(ans[i]>0)<<" ";
return 0;
}
T3
首先转化为区间问题,然后用单调队列求出每杯奶茶的覆盖区域 \([l,r]\),转一维为二维,问题即转化为给横坐标为 \([l,i]\),纵坐标为 \([i,r]\) 的区间加上奶茶的卡路里数,差分即可。
时间复杂度 \(O(nm+m^2)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005,M=205;
int n,m,a[N][M],d[N],tp,st[N];
int sm[N][N],lg[N],rg[N],ans;
signed main(){
freopen("calorie.in","r",stdin);
freopen("calorie.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=2;i<=m;i++)
cin>>d[i],d[i]+=d[i-1];
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
while(tp&&a[st[tp]][j]<=a[i][j]) tp--;
lg[i]=st[tp]+1,st[++tp]=i;
}tp=0;
for(int i=m;i;i--){
while(tp&&a[st[tp]][j]<a[i][j]) tp--;
rg[i]=(!tp?m:st[tp]-1),st[++tp]=i;
}tp=0;
for(int i=1;i<=m;i++){
sm[lg[i]][i]+=a[i][j],sm[i+1][rg[i]+1]+=a[i][j];
sm[i+1][i]-=a[i][j],sm[lg[i]][rg[i]+1]-=a[i][j];
}
}for(int i=1;i<=m;i++) for(int j=1;j<=m;j++)
sm[i][j]+=sm[i-1][j]+sm[i][j-1]-sm[i-1][j-1];
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
ans=max(ans,sm[i][j]-d[j]+d[i]);
cout<<ans;
return 0;
}
T4
注意力惊人地注意到原问题可以转化为构造序列 \(t_i\),满足当 \(t_j>t_i,j>i,\max\limits_{k=i+1}^{j-1}t_k\le i\) 时,\(s_i\ne s_j\),很明显是可 \(dp\) 的。设 \(f_{l,r,i}\) 表示区间 \([l,r]\) 中最大值为 \(i\) 时的方案数,预处理前缀和可以达到 \(O(nm^3)\)。继续注意力惊人的注意到 \(f_{l,r}\) 是关于 \(i\) 的 \(r-l\) 次函数,\(sm_{l,r}\) 是关于 \(i\) 的 \(r-l+1\) 次函数,所以直接求出 \(sm_{1,m,k}(0\le k\le m+1)\) 后,拉格朗日插值插出 \(n\) 时的答案即可。
时间复杂度 \(O(m^4)\),注意卡常。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=205,p=1e9+7;
int n,m,s[N],tot,f[N][N][N];
int qpow(int x,int y){
int re=1;
while(y){
if(y&1) re=re*x%p;
x=x*x%p,y>>=1;
}return re;
}int lag(int x){
int re=0;
for(int i=0,ml=1;i<=m;i++,ml=1){
for(int j=0;j<=m;j++) if(i!=j)
ml=ml*(x-j)%p*qpow((i-j+p)%p,p-2)%p;
re=(re+f[1][m][i]*ml)%p;
}return re;
}signed main(){
freopen("message.in","r",stdin);
freopen("message.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>s[i];
for(int i=1;i<=m;i++){
if(s[i]==s[i+1]) continue;
for(int j=1;j<=m;j++) f[i][i][j]=j;
}for(int ln=2;ln<=m;ln++)
for(int l=1,r=ln;r<=m;l++,r++){
for(int i=1;i<=m;i++){
if(s[l]!=s[r+1])
f[l][r][i]=(f[l][r][i]+f[l+1][r][i])%p;
if(s[r]!=s[r+1])
f[l][r][i]=(f[l][r][i]+f[l][r-1][i-1])%p;
}for(int k=l+1;k<r;k++) if(s[k]!=s[r+1]) for(int i=1;i<=m;i++)
f[l][r][i]=(f[l][r][i]+f[l][k-1][i-1]*f[k+1][r][i])%p;
for(int i=1;i<=m;i++)
f[l][r][i]=(f[l][r][i]+f[l][r][i-1])%p;
}
cout<<lag(n);
return 0;
}