由于题都比较简单,全部写在一起了。
T1
定义树上的毛毛虫是一条链和所有和这条链相邻的点的集合。
给一棵树,请你维护。修改为选择一个毛毛虫,给所有点加上某个权值。
询问为给一个毛毛虫,求点权值之和。
口胡:
每个点维护所有儿子的权值之和。于是修改变成了链加,最后处理lca和父亲的权值。询问同理。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
namespace IO{
inline char gc(){
static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}template<typename T>T get_integer(){
char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
}inline int gi(){return get_integer<int>();}
}using namespace IO;
using std::cerr;
using std::cout;
cs int N=1e5+7;
int n,Q;
int el[N],nx[N<<1|1],to[N<<1|1],ec;
inline void adde(int u,int v){
nx[++ec]=el[u],el[u]=ec,to[ec]=v;
nx[++ec]=el[v],el[v]=ec,to[ec]=u;
}
int dfn[N],dfc,ps[N];
int fa[N],d[N],top[N];
int sz[N],son[N],deg[N];
void dfs1(int u,int p){
fa[u]=p,d[u]=d[p]+1;sz[u]=1;
for(int re e=el[u];e;e=nx[e])
if(to[e]!=p){
dfs1(to[e],u);sz[u]+=sz[to[e]];++deg[u];
if(sz[to[e]]>sz[son[u]])son[u]=to[e];
}
}
void dfs2(int u,int tp){
dfn[u]=++dfc,ps[dfc]=u;top[u]=tp;
if(son[u])dfs2(son[u],tp);
for(int re e=el[u];e;e=nx[e])
if(to[e]!=fa[u]&&to[e]!=son[u])
dfs2(to[e],to[e]);
}
ll w[N];ll sm[N];
namespace SGT{
cs int N=::N<<2|7;
#define lc u<<1
#define rc u<<1|1
ll tg[N],dsm[N],sm[N];
void build(int u,int l,int r){
if(l==r){
sm[u]=::sm[ps[l]];
dsm[u]=::deg[ps[l]];
tg[u]=0;return;
}int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
dsm[u]=dsm[lc]+dsm[rc];sm[u]=sm[lc]+sm[rc];
}
void push_nw(int u,ll w){
tg[u]+=w;sm[u]+=w*dsm[u];
}
void push_dn(int u){
if(tg[u]){
push_nw(lc,tg[u]);
push_nw(rc,tg[u]);
tg[u]=0;
}
}
void add_ch(int u,int l,int r,int ql,int qr,int w){
if(ql<=l&&r<=qr)return push_nw(u,w);
int mid=(l+r)>>1;push_dn(u);
if(ql<=mid)add_ch(lc,l,mid,ql,qr,w);
if(mid<qr)add_ch(rc,mid+1,r,ql,qr,w);
sm[u]=sm[lc]+sm[rc];
}
void add_fa(int u,int l,int r,int p,int w){
if(l==r){sm[u]+=w;return ;}int mid=(l+r)>>1;push_dn(u);
p<=mid?add_fa(lc,l,mid,p,w):add_fa(rc,mid+1,r,p,w);sm[u]=sm[lc]+sm[rc];
}
ll query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return sm[u];
int mid=(l+r)>>1;push_dn(u);
if(qr<=mid)return query(lc,l,mid,ql,qr);
if(mid<ql)return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
}
ll query_tg(int u,int l,int r,int p){
if(l==r){return tg[u];}int mid=(l+r)>>1;push_dn(u);
return p<=mid?query_tg(lc,l,mid,p):query_tg(rc,mid+1,r,p);
}
}
void Add(){
int u=gi(),v=gi(),w=gi();
while(top[u]!=top[v]){
if(d[top[u]]<d[top[v]])std::swap(u,v);
SGT::add_ch(1,1,n,dfn[top[u]],dfn[u],w);u=fa[top[u]];
}if(d[u]>d[v])std::swap(u,v);
SGT::add_ch(1,1,n,dfn[u],dfn[v],w);
if(fa[u])SGT::add_fa(1,1,n,dfn[fa[u]],w);
if(fa[fa[u]])SGT::add_fa(1,1,n,dfn[fa[fa[u]]],w);
::w[u]+=w;if(fa[u])::w[fa[u]]+=w;
}
void Query(){
int u=gi(),v=gi();ll ans=0;
while(top[u]!=top[v]){
if(d[top[u]]<d[top[v]])std::swap(u,v);
ans+=SGT::query(1,1,n,dfn[top[u]],dfn[u]);u=fa[top[u]];
}if(d[u]>d[v])std::swap(u,v);
ans+=SGT::query(1,1,n,dfn[u],dfn[v]);
if(fa[u])ans+=SGT::query_tg(1,1,n,dfn[fa[u]]);
if(fa[fa[u]])ans+=SGT::query_tg(1,1,n,dfn[fa[fa[u]]]);
ans+=w[u];if(fa[u])ans+=w[fa[u]];cout<<ans<<"\n";
}
void Main(){n=gi();
for(int re i=1;i<n;++i)
adde(gi(),gi());
dfs1(1,0),dfs2(1,1);
for(int re i=1;i<=n;++i)
sm[fa[i]]+=(w[i]=gi());
SGT::build(1,1,n);Q=gi();
while(Q--)switch(gi()){
case 1:Add();break;
case 2:Query();break;
}
}
inline void file(){
#ifdef zxyoi
freopen("caterpillar.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
freopen("caterpillar.in","r",stdin);
freopen("caterpillar.out","w",stdout);
#endif
#endif
}signed main(){file();Main();return 0;}
T2:
给 n n n 个怪,你知道血量,但不知道血量对应的怪,你每次可以选择一个怪攻击,使其血量-1,血量为0的怪死亡,你知道每个怪之前被你打了几下以及哪些怪已经死亡。
请你求出在最优策略下,打死至少 m m m 只怪要用最少攻击次数。
读了一半还以为是交互题。
首先如果是交互题,写交互库的思路非常显然,按照被打的次数分集合,每次打的时候把集合中最大的一个拿出去放到下一个集合。
显然最优策略要面对就是这种情况。
排序,假设确定了我们要打的怪,怎么打?策略非常显然,按照血量排序,所有怪先打 a 1 a_1 a1 次可以把要打的最小怪打死,然后把没死的怪拿来打 a 2 − a 1 a_2-a_1 a2−a1 次,依次类推。
我的 DP 和 std 有点不一样。先将 a i a_i ai 降序排序。
设状态 f [ j ] [ i ] f[j][i] f[j][i] 表示考虑前 i i i 个怪,已经打死了 j j j 个怪需要的最少次数。
转移显然是 f [ j ] [ i ] = min k < i { f [ j − 1 ] [ k ] + a [ i ] ∗ ( i − k ) } = a [ i ] ∗ i + min k < i { f [ j − 1 ] [ k ] − a [ i ] ∗ k } f[j][i]=\min_{k<i}\{f[j-1][k]+a[i]*(i-k)\}=a[i]*i+\min_{k<i}\{f[j-1][k]-a[i]*k\} f[j][i]=mink<i{f[j−1][k]+a[i]∗(i−k)}=a[i]∗i+mink<i{f[j−1][k]−a[i]∗k}
斜率优化即可,维护下凸壳。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using std::cerr;
using std::cout;
cs int N=2e3+7;
int n,m;
int a[N];
ll f[N][N];
void Main(){
scanf("%d%d",&n,&m);
for(int re i=1;i<=n;++i)
scanf("%d",a+i);
std::sort(a+1,a+n+1);
std::reverse(a+1,a+n+1);
f[0][0]=0;
std::fill(f[0]+1,f[0]+n+1,2e9);
for(int re j=1;j<=m;++j){
std::fill(f[j],f[j]+n+1,2e9);
static ll x[N],y[N];int tp=1;
x[tp]=0,y[tp]=f[j-1][0];
for(int re i=1;i<=n;++i){
auto calc=[i](int id){
return y[id]-(ll)a[i]*x[id];
};int l=1,r=tp;
while(l<r){
int mid=(l+r)>>1;
if(calc(mid)<calc(mid+1))r=mid;
else l=mid+1;
}f[j][i]=(ll)a[i]*i+calc(l);
auto crs=[&](int X,int Y){
int x1=x[tp]-x[tp-1],y1=y[tp]-y[tp-1];
int x2=X-x[tp],y2=Y-y[tp];
return (ll)x1*y2-(ll)y1*x2;
};
while(tp>1&&crs(i,f[j-1][i])<=0)--tp;
++tp;y[tp]=f[j-1][i],x[tp]=i;
}
}
cout<<*std::max_element(f[m],f[m]+n+1,std::greater<ll>())<<"\n";
}
inline void file(){
#ifdef zxyoi
freopen("medium.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
freopen("medium.in","r",stdin);
freopen("medium.out","w",stdout);
#endif
#endif
}signed main(){file();int T;scanf("%d",&T);while(T--)Main();return 0;}
T3
一个长为 n n n 的数列 a a a,删去 k k k 个数之后,向左补位得到长为 n − k n-k n−k 的数列 b b b。
请你求出 b i = i b_i=i bi=i 最多能出现多少次。
显然 最后可能满足条件的位置必然满足 a i ≤ n − k , a i ≥ i , i ≤ a i + k a_i\leq n-k,a_i\geq i,i\leq a_i+k ai≤n−k,ai≥i,i≤ai+k,先把不合法的去掉。
容易发现我们求的是三维偏序的 LIS ,即 i < j , a i < a j , i − a i ≤ j − a j i<j,a_i<a_j,i-a_i\leq j-a_j i<j,ai<aj,i−ai≤j−aj,容易发现满足后面两条的时候第一条自动满足,于是转化为二维偏序,一个log 解决。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
namespace IO{
inline char gc(){
static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}template<typename T>T get_integer(){
char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
}inline int gi(){return get_integer<int>();}
}using namespace IO;
using std::cerr;
using std::cout;
typedef std::pair<int,int> pii;
#define fi first
#define se second
cs int N=5e5+7;
int n,k;
int a[N],len;
std::vector<pii> vec;
void Main(){
n=gi(),k=gi();
for(int re i=1;i<=n;++i){
int v=gi(),b=i-v;
if(v>n-k||i<v||i>v+k)continue;
vec.push_back(pii(b,v));
}std::sort(vec.begin(),vec.end());a[0]=-1;
for(size_t re i=0;i<vec.size();++i){
int v=vec[i].se;
if(v>a[len])a[++len]=v;
else *std::lower_bound(a+1,a+len+1,v)=v;
}cout<<len<<"\n";
}
inline void file(){
#ifdef zxyoi
freopen("maximize.in","r",stdin);
#else
#ifndef ONLINE_JUDGE
freopen("maximize.in","r",stdin);
freopen("maximize.out","w",stdout);
#endif
#endif
}signed main(){file();Main();return 0;}