JOISC2019 题解
A. Examination
题目大意
有
个二元组 和 个询问 ,每个询问求满足 , , 的二元组个数。 数据范围:
。
思路分析
直接 CDQ 求三维偏序即可,注意实现的常数。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+1;
int ans[MAXN];
struct Node {
int u,v,w,id;
} a[MAXN];
class FenwickTree {
private:
int tree[MAXN];
inline int lowbit(int x) { return x&-x; }
vector <int> used;
public:
inline void Modify(int x,int v) {
used.push_back(x);
for(;x;x-=lowbit(x)) tree[x]+=v;
}
inline int Query(int x) {
int ret=0;
for(;x<MAXN;x+=lowbit(x)) ret+=tree[x];
return ret;
}
inline void Clear() {
for(int x:used) for(;x;x-=lowbit(x)) tree[x]=0;
used.clear();
}
} TR;
inline void CDQ(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
for(int lp=l,rp=mid;lp<=mid;++lp) {
while(rp<r&&a[lp].v<=a[rp+1].v) {
if(!a[++rp].id) TR.Modify(a[rp].w,1);
}
if(a[lp].id) ans[a[lp].id]+=TR.Query(a[lp].w);
}
TR.Clear();
inplace_merge(a+l,a+mid+1,a+r+1,[&](Node x,Node y){ return x.v>y.v; });
}
signed main() {
int n,q,N=0;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) {
int s,t;
scanf("%d%d",&s,&t);
a[++N]={s,t,s+t,0};
}
for(int i=1;i<=q;++i) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[++N]={x,y,z,i};
}
vector <int> X,Y,Z;
for(int i=1;i<=N;++i) X.push_back(a[i].u),Y.push_back(a[i].v),Z.push_back(a[i].w);
sort(X.begin(),X.end()),X.erase(unique(X.begin(),X.end()),X.end());
sort(Y.begin(),Y.end()),Y.erase(unique(Y.begin(),Y.end()),Y.end());
sort(Z.begin(),Z.end()),Z.erase(unique(Z.begin(),Z.end()),Z.end());
for(int i=1;i<=N;++i) {
a[i].u=lower_bound(X.begin(),X.end(),a[i].u)-X.begin()+1;
a[i].v=lower_bound(Y.begin(),Y.end(),a[i].v)-Y.begin()+1;
a[i].w=lower_bound(Z.begin(),Z.end(),a[i].w)-Z.begin()+1;
}
sort(a+1,a+N+1,[&](Node x,Node y){
if(x.u!=y.u) return x.u<y.u;
else if(x.v!=y.v) return x.v<y.v;
else if(x.w!=y.w) return x.w<y.w;
else return x.id>y.id;
});
CDQ(1,N);
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
B. Meetings
题目大意
交互器有一棵
个点的树,每次你可以询问 ,交互器会回答 即离他们距离和最小的点。 请在
次询问内还原整棵树。 数据范围:
,每个点度数不超过 。
思路分析
考虑不断扩展一个连通块,先从已知连通块为单点
那么我们就知道删掉这条路径后形成的森林的每个连通块的根和点集,递归求解即可。
问题只剩对链排序,容易发现
操作次数和时间复杂度不会证。
代码呈现
#include<bits/stdc++.h>
#include"meetings.h"
using namespace std;
mt19937 rnd(time(0));
void dfs(int rt,vector<int>&ver) {
map <int,vector<int>> tr;
vector <int> pat;
int p=ver[rnd()%ver.size()];
for(int u:ver) if(u!=p) {
int v=Query(rt,p,u);
if(v==u) pat.push_back(u);
else tr[v].push_back(u);
}
sort(pat.begin(),pat.end(),[&](int x,int y){ return Query(rt,x,y)==x; });
pat.insert(pat.begin(),rt),pat.push_back(p);
for(int i=1;i<(int)pat.size();++i) Bridge(min(pat[i-1],pat[i]),max(pat[i-1],pat[i]));
for(int u:pat) if(tr[u].size()) dfs(u,tr[u]);
}
void Solve(int N) {
vector <int> ver(N-1);
iota(ver.begin(),ver.end(),1);
dfs(0,ver);
}
C. Naan
题目大意
有一个
厘米的线段,第 厘米的部分被染上了颜色 。 另有
个人,每个人对每个颜色有一个喜好程度 ,现在要将线段分割成连续的 段,每段给一个人。 每个人得到一段线段的开心值是线段上每个颜色对应的喜好程度乘上长度。最终的方案要满足第
个人的开心值大于等于 。 给出方案或报告无解,输出分数,要求分母
。
, 。
思路分析
对于每个人
然后第一段就取
由于
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=2005;
struct frac {
ll x,y;
inline bool friend operator <(const frac &u,const frac &v) {
return (LL)u.x*v.y<(LL)v.x*u.y;
}
} pos[MAXN][MAXN];
int n,m,bel[MAXN];
bool vis[MAXN];
ll a[MAXN][MAXN],tar[MAXN],tmp[MAXN];
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
scanf("%lld",&a[i][j]);
tar[i]+=a[i][j];
}
}
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) tmp[j]=a[i][j]*n;
for(int j=1,p=1;j<n;++j) {
ll rem=tar[i];
while(rem>tmp[p]) rem-=tmp[p++];
tmp[p]-=rem;
pos[i][j].y=n*a[i][p];
pos[i][j].x=p*pos[i][j].y-tmp[p];
}
}
for(int i=1;i<n;++i) {
int p=0;
for(int j=1;j<=n;++j) {
if(!vis[j]&&(!p||pos[j][i]<pos[p][i])) p=j;
}
bel[i]=p,vis[p]=true;
printf("%lld %lld\n",pos[p][i].x,pos[p][i].y);
}
for(int i=1;i<=n;++i) if(!vis[i]) bel[n]=i;
for(int i=1;i<=n;++i) printf("%d ",bel[i]); puts("");
return 0;
}
D. Two Anetennas
题目大意
给定
个点,每个点 会向标号在 范围内的人连一条有向边。
次询问 之中所有双向连边的点 ,最大的 是多少。 数据范围:
。
思路分析
显然
考虑扫描线,线段树维护每个
对于每个
注意删除
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,inf=1e9;
int n,q;
struct SegmentTree {
int tag[MAXN<<2],lmx[MAXN<<2],rmx[MAXN<<2];
inline int L(int p) { return p<<1; }
inline int R(int p) { return p<<1|1; }
inline void psu(int p) {
lmx[p]=max(lmx[L(p)],lmx[R(p)]);
rmx[p]=max(rmx[L(p)],rmx[R(p)]);
}
inline void adt(int p,int k) {
tag[p]=max(tag[p],k);
rmx[p]=max(rmx[p],lmx[p]+k);
}
inline void psd(int p) {
if(tag[p]!=-inf) adt(L(p),tag[p]),adt(R(p),tag[p]),tag[p]=-inf;
}
inline void build(int l=1,int r=n,int p=1) {
tag[p]=lmx[p]=rmx[p]=-inf;
if(l==r) return ;
int mid=(l+r)>>1;
build(l,mid,L(p)),build(mid+1,r,R(p));
}
inline void updl(int u,int k,int l=1,int r=n,int p=1) {
if(l==r) return lmx[p]=k,void();
int mid=(l+r)>>1; psd(p);
if(u<=mid) updl(u,k,l,mid,L(p));
else updl(u,k,mid+1,r,R(p));
psu(p);
}
inline void updr(int ul,int ur,int k,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) updr(ul,ur,k,l,mid,L(p));
if(mid<ur) updr(ul,ur,k,mid+1,r,R(p));
psu(p);
}
inline int qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return rmx[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qry(ul,ur,l,mid,L(p));
if(mid<ul) return qry(ul,ur,mid+1,r,R(p));
return max(qry(ul,ur,l,mid,L(p)),qry(ul,ur,mid+1,r,R(p)));
}
} TR;
struct Query { int l,r,id; };
vector <Query> Q[MAXN];
int h[MAXN],a[MAXN],b[MAXN],ans[MAXN];
vector <int> ins[MAXN<<1],ers[MAXN<<1];
inline void solve() {
TR.build();
for(int i=1;i<=n;++i) {
for(int j:ins[i]) TR.updl(j,-h[j]);
if(i>a[i]) TR.updr(max(1,i-b[i]),i-a[i],h[i]);
for(auto x:Q[i]) ans[x.id]=max(ans[x.id],TR.qry(x.l,x.r));
for(int j:ers[i]) TR.updl(j,-inf);
}
}
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d%d%d",&h[i],&a[i],&b[i]);
ins[i+a[i]].push_back(i),ers[i+b[i]].push_back(i);
}
scanf("%d",&q);
for(int i=1,l,r;i<=q;++i) scanf("%d%d",&l,&r),Q[r].push_back({l,r,i});
fill(ans+1,ans+q+1,-1);
solve();
for(int i=1;i<=n;++i) h[i]*=-1;
solve();
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
E. Two Dishes
题目大意
给一个长度为
的序列 和一个长度为 的序列 ,现在要将在保证相对顺序的前提下合并两个序列。 每个元素有三个参数
,表示任务 需要 秒完成,在第 秒前完成会获得 的分数。 求最大可能的分数和。
数据范围:
。
思路分析
对于
同理
考虑统一转化模型:
- 对
的限制等价于选 的时候如果已经选了 就会有 的贡献。 - 对
的限制考虑反面考虑,先把 加入答案,选 的时候如果已经选了 就会有 的贡献。
然后考虑维护
用 std::set
或 std::map
维护差分数组,取前缀
显然每个差分项只会被删除一次,因此复杂度是对的。
注意我们实际上是每加一个数就取一次前缀
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
const ll inf=1e18;
ll a[MAXN],s[MAXN],p[MAXN],b[MAXN],t[MAXN],q[MAXN],ans=0;
vector <array<ll,2>> f[MAXN];
map <int,ll> dp;
inline void add(int id,ll val) {
for(auto it=dp.lower_bound(id);val<0;it=dp.erase(it)) {
id=it->first,val+=it->second;
}
dp[id]+=val;
}
signed main() {
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i]>>s[i]>>p[i],a[i]+=a[i-1];
for(int i=1;i<=m;++i) cin>>b[i]>>t[i]>>q[i],b[i]+=b[i-1];
for(int i=1;i<=n;++i) if(s[i]>=a[i]) {
int j=upper_bound(b+1,b+m+1,s[i]-a[i])-b;
ans+=p[i];
if(j<=m) f[i].push_back({j,-p[i]});
}
for(int j=1;j<=m;++j) if(t[j]>=b[j]) {
int i=upper_bound(a+1,a+n+1,t[j]-b[j])-a;
if(i<=n) f[i].push_back({j,q[j]});
else ans+=q[j];
}
dp[m+1]=inf;
for(int i=1;i<=n;++i) {
sort(f[i].begin(),f[i].end(),[&](auto x,auto y){ return x[1]>y[1]; });
for(auto k:f[i]) add(k[0],k[1]);
}
for(auto x:dp) if(x.first<=m) ans+=x.second;
cout<<ans<<"\n";
return 0;
}
F. Two Transportations
题目大意
通信题。
给一张
个点 条带权无向边的图,其中 Azer.cpp
知道前条边构成的图 , Baijan.cpp
知道后条边构成的图 。 两个人都可以向对方发送信息(以二进制位形式),但相互发送信息的总量不能超过
个 。 最终要让
Azer.cpp
输出到其他点的最短路。 数据范围:
。
思路分析
考虑 Dijkstra,设已拓展点集为
那么做法就很简单了,先求出
一次拓展的代价是
时间复杂度
代码呈现
Azer.cpp
:
#include<bits/stdc++.h>
#include "Azer.h"
using namespace std;
const int inf=1e9;
int n,m,dA,dB,lstdis,vA,vB,typ,rdcnt,updcnt;
//typ=0:read dis,typ=1:read ver
vector <int> dis,vis;
struct Edge { int v,w; };
vector <vector<Edge>> G;
void upd(int u) {
vis[u]=true,lstdis=dis[u];
if(++updcnt==n) return ;
for(auto e:G[u]) dis[e.v]=min(dis[e.v],dis[u]+e.w);
vA=-1,dA=lstdis+511;
for(int i=0;i<n;++i) if(!vis[i]&&dA>dis[i]) vA=i,dA=dis[i];
dA-=lstdis,typ=0,rdcnt=9,dB=0;
for(int k=8;~k;--k) SendA((dA>>k)&1);
}
void InitA(int N,int A,vector<int> U,vector<int> V,vector<int> C) {
n=N,m=A,dis.resize(n,inf),vis.resize(n),G.resize(n);
for(int i=0;i<m;++i) {
G[U[i]].push_back({V[i],C[i]});
G[V[i]].push_back({U[i],C[i]});
}
dis[0]=0,upd(0);
}
void ReceiveA(bool x) {
if(typ) vB=vB*2+x;
else dB=dB*2+x;
if(!--rdcnt) {
if(typ) dis[vB]=lstdis+dB,upd(vB);
else {
if(dA<=dB) {
for(int k=10;~k;--k) SendA((vA>>k)&1);
dis[vA]=lstdis+dA,upd(vA);
} else typ=1,rdcnt=11,vB=0;
}
}
}
vector<int> Answer() { return dis; }
Baijan.cpp
:
#include<bits/stdc++.h>
#include "Baijan.h"
using namespace std;
const int inf=1e9;
int n,m,dA,dB,lstdis,vA,vB,typ,rdcnt,updcnt;
//typ=0:read dis,typ=1:read ver
vector <int> dis,vis;
struct Edge { int v,w; };
vector <vector<Edge>> G;
void upd(int u) {
vis[u]=true,lstdis=dis[u];
if(++updcnt==n) return ;
for(auto e:G[u]) dis[e.v]=min(dis[e.v],dis[u]+e.w);
vB=-1,dB=lstdis+511;
for(int i=0;i<n;++i) if(!vis[i]&&dB>dis[i]) vB=i,dB=dis[i];
dB-=lstdis,typ=0,rdcnt=9,dA=0;
for(int k=8;~k;--k) SendB((dB>>k)&1);
}
void InitB(int N,int B,vector<int> U,vector<int> V,vector<int> C) {
n=N,m=B,dis.resize(n,inf),vis.resize(n),G.resize(n);
for(int i=0;i<m;++i) {
G[U[i]].push_back({V[i],C[i]});
G[V[i]].push_back({U[i],C[i]});
}
dis[0]=0,upd(0);
}
void ReceiveB(bool x) {
if(typ) vA=vA*2+x;
else dA=dA*2+x;
if(!--rdcnt) {
if(typ) dis[vA]=lstdis+dA,upd(vA);
else {
if(dB<dA) {
for(int k=10;~k;--k) SendB((vB>>k)&1);
dis[vB]=lstdis+dB,upd(vB);
} else typ=1,rdcnt=11,vA=0;
}
}
}
G. Designated Cities
题目大意
给定一棵树,每条边有正向和反向,正向边权值为
,反向边权值为 。 定义一次操作为:选择一个点
,然后把以 为根的内向树上的有向边全部删掉。
次询问,每次给定 ,求出进行 次操作后剩余的边权值和最小是多少。 数据范围:
。
思路分析
考虑贪心,可以证明在
以直径一端为根,每次操作就是删掉一条链,这显然是经典的贪心结论,长链剖分后排序即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
struct Edge { int v,x,y; }; //x:u->v,y:v<-u
vector <Edge> G[MAXN];
int n,q,fa[MAXN];
bool ind[MAXN];
ll sum=0,ans[MAXN],rt[MAXN],dis[MAXN];
inline void dfs0(int u,int fz) {
for(auto e:G[u]) if(e.v^fz) rt[1]+=e.y,dfs0(e.v,u);
}
inline void dfs1(int u,int fz) {
ans[1]=max(ans[1],rt[u]);
for(auto e:G[u]) if(e.v^fz) rt[e.v]=rt[u]-e.y+e.x,dfs1(e.v,u);
}
inline void dfs2(int u,int fz,ll d,int &p) {
fa[u]=fz,dis[u]=d;
if(!p||dis[u]+rt[u]>dis[p]+rt[p]) p=u;
for(auto e:G[u]) if(e.v^fz) dfs2(e.v,u,d+e.x+e.y,p);
}
vector <ll> len;
inline ll dfs3(int u,int fz) {
ll mx=0;
for(auto e:G[u]) if(e.v^fz) {
ll tmp=e.x+dfs3(e.v,u);
if(!mx) mx=tmp;
else len.push_back(min(mx,tmp)),mx=max(mx,tmp);
}
return mx;
}
signed main() {
scanf("%d",&n);
for(int i=1,u,v,x,y;i<n;++i) {
scanf("%d%d%d%d",&u,&v,&x,&y);
sum+=x+y;
G[u].push_back({v,x,y});
G[v].push_back({u,y,x});
}
dfs0(1,0),dfs1(1,0);
int S=0,T=0;
dfs2(1,0,0,S);
dfs2(S,0,0,T);
ans[2]=(rt[S]+rt[T]+dis[T])>>1;
for(int u=T;u;u=fa[u]) ind[u]=true;
for(int u=T;u;u=fa[u]) {
for(auto e:G[u]) if(e.v!=fa[u]&&!ind[e.v]) {
len.push_back(dfs3(e.v,u)+e.x);
}
}
sort(len.begin(),len.end(),greater<ll>());
for(int i=0;i<(int)len.size();++i) ans[i+3]=ans[i+2]+len[i];
for(int lst=len.size()+2,i=lst+1;i<=n;++i) ans[i]=ans[lst];
scanf("%d",&q);
for(int x;q--;) scanf("%d",&x),printf("%lld\n",sum-ans[x]);
return 0;
}
H. Lamps
题目大意
给定两个 01 串
,每次操作可以对 进行区间覆盖或区间取反,求 的最小操作次数。 数据范围:
。
思路分析
考虑观察解的形态:如果有一个取反操作在覆盖操作前面,显然可以通过调整把取反操作调整到覆盖操作后面。
而且显然取反操作区间不交,因此我们进行若干次覆盖得到
显然简单 dp 即可,
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int n,dp[MAXN][3]; //0:set 0,1:set 1,2:do nothing
char a[MAXN],b[MAXN];
signed main() {
scanf("%d%s%s",&n,a+1,b+1);
memset(dp,0x3f,sizeof(dp));
dp[1][0]=1+(b[1]!='0');
dp[1][1]=1+(b[1]!='1');
dp[1][2]=(b[1]!=a[1]);
for(int i=2;i<=n;++i) for(int x:{0,1,2}) for(int y:{0,1,2}) {
int c=dp[i-1][x];
if(y<2&&x!=y) ++c;
char vx=(x==2?a[i-1]:x+'0'),vy=(y==2?a[i]:y+'0');
if(b[i]!=vy&&b[i-1]==vx) ++c;
dp[i][y]=min(dp[i][y],c);
}
printf("%d\n",min({dp[n][0],dp[n][1],dp[n][2]}));
return 0;
}
I. Bitaro, who Leaps through Time
题目大意
数轴上有
个城市,其中 之间有双向路径,想要通过必须在 时刻出发,在 到达另一侧,你可以在原地让时刻 或发动时光倒流使时刻 。
次操作,修改 或询问 时刻在 至少要几次时光倒流才能在时刻 到 。 数据范围:
。
思路分析
可以看成在网格图中向右下、下或上移动,考虑把右下操作变成右,即令
然后这个问题就可以直接贪心,每次移动尽可能少,那么一个区间的限制有两种:
表示在 之内可以向另一侧移动。 表示从 时刻进入 时刻输出,花费的代价为 。
分类讨论合并信息即可,线段树维护。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+5;
struct Info {
int x,y; ll p; //p=-1: interval[x,y], p>=0: path x->y, cost = p
inline friend Info operator +(const Info &u,const Info &v) {
if(~u.p&&~v.p) return {u.x,v.y,u.p+max(0,u.y-v.x)+v.p};
if(~u.p) return {u.x,max(min(u.y,v.y),v.x),u.p+max(0,u.y-v.y)};
if(~v.p) return {min(max(u.x,v.x),u.y),v.y,v.p+max(0,u.x-v.x)};
if(u.y<v.x) return {u.y,v.x,0};
if(u.x>v.y) return {u.x,v.y,u.x-v.y};
return {max(u.x,v.x),min(u.y,v.y),-1};
}
};
int n,q;
struct SegmentTree {
Info tr[MAXN<<2];
inline void psu(int p) { tr[p]=tr[p<<1]+tr[p<<1|1]; }
inline void upd(int u,Info I,int l=1,int r=n,int p=1) {
if(l==r) return tr[p]=I,void();
int mid=(l+r)>>1;
if(u<=mid) upd(u,I,l,mid,p<<1);
else upd(u,I,mid+1,r,p<<1|1);
psu(p);
}
inline Info qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1;
if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
return qry(ul,ur,l,mid,p<<1)+qry(ul,ur,mid+1,r,p<<1|1);
}
};
struct sol {
SegmentTree T;
inline void set(int i,int x,int y) { T.upd(i,{x-i,y-i-1,-1}); }
inline ll qry(int u,int s,int v,int t) {
return max(((Info){s-u,s-u,-1}+T.qry(u,v-1)+(Info){t-v,t-v,-1}).p,0ll);
}
} T1,T2;
int l[MAXN],r[MAXN];
signed main() {
scanf("%d%d",&n,&q);
for(int i=1;i<n;++i) {
scanf("%d%d",&l[i],&r[i]);
T1.set(i,l[i],r[i]),T2.set(n-i,l[i],r[i]);
}
for(int op,u,v,s,t;q--;) {
scanf("%d",&op);
if(op==1) {
scanf("%d%d%d",&u,&s,&t);
T1.set(u,s,t),T2.set(n-u,s,t);
} else {
scanf("%d%d%d%d",&u,&s,&v,&t);
if(u==v) printf("%d\n",max(0,s-t));
if(u<v) printf("%lld\n",T1.qry(u,s,v,t));
if(u>v) printf("%lld\n",T2.qry(n-u+1,s,n-v+1,t));
}
}
return 0;
}
J. Cake 3
题目大意
在
个元素中选 个并进行排列得到 ,最大化 。 数据范围:
。
思路分析
显然
这是经典的决策单调性模型,分治优化,主席树维护权值即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,inf=1e9;
const ll infll=1e18;
struct SegmentTree {
int ls[MAXN<<5],rs[MAXN<<5],cnt[MAXN<<5],tot;
ll sum[MAXN<<5];
inline void ins(int u,int l,int r,int q,int &p) {
p=++tot,cnt[p]=cnt[q]+1,sum[p]=sum[q]+u;
if(l==r) return ;
int mid=(l+r)>>1;
if(u<=mid) ins(u,l,mid,ls[q],ls[p]),rs[p]=rs[q];
else ins(u,mid+1,r,rs[q],rs[p]),ls[p]=ls[q];
}
inline ll qry(int k,int l,int r,int q,int p) {
if(l==r) return min(k,cnt[p])*l;
int mid=(l+r)>>1,rc=cnt[rs[p]]-cnt[rs[q]];
if(k<=rc) return qry(k,mid+1,r,rs[q],rs[p]);
return sum[rs[p]]-sum[rs[q]]+qry(k-rc,l,mid,ls[q],ls[p]);
}
} T;
struct node { int h,v; } a[MAXN];
int n,m,rt[MAXN];
ll dp[MAXN];
inline void solve(int l,int r,int L,int R) {
if(l>r) return ;
int mid=(l+r)>>1,M=0;
for(int i=L;i<=R&&i<=mid-m+1;++i) {
ll val=T.qry(m,1,inf,rt[i-1],rt[mid])-2*(a[mid].h-a[i].h);
if(val>dp[mid]) dp[mid]=val,M=i;
}
solve(l,mid-1,L,M),solve(mid+1,r,M,R);
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d%d",&a[i].v,&a[i].h);
sort(a+1,a+n+1,[&](auto x,auto y){ return x.h<y.h; });
for(int i=1;i<=n;++i) T.ins(a[i].v,1,inf,rt[i-1],rt[i]);
memset(dp,-0x3f,sizeof(dp));
solve(m,n,1,n);
ll ans=-infll;
for(int i=m;i<=n;++i) ans=max(ans,dp[i]);
printf("%lld\n",ans);
return 0;
}
K. Mergers
题目大意
给定一棵
个点的树,每个点有颜色,定义一条边是分裂的当且仅当删掉这条边后形成了的两个连通块没有公共的颜色。 每次操作可以更改某类颜色的所有点变成另一个颜色,求让整棵树的边都不是分裂的的最小操作次数。
数据范围:
。
思路分析
先求出所有分裂的边,然后把不分裂的边缩起来,形成了一棵新的树,每次操作相当于选定两个点,把对应的路径上的分裂边变成不分裂的。
然后变成选若干路径覆盖整棵树,这是经典结论,设
求分裂的边直接树上差分,把同色点之间的路径用树上差分打标记设成不分裂的。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
vector <int> G[MAXN],C[MAXN];
int col[MAXN],fa[MAXN][20],dep[MAXN],cnt[MAXN],deg[MAXN];
inline void dfs0(int u,int fz) {
fa[u][0]=fz,dep[u]=dep[fz]+1;
for(int k=1;k<20;++k) fa[u][k]=fa[fa[u][k-1]][k-1];
for(int v:G[u]) if(v^fz) dfs0(v,u);
}
inline void dfs1(int u,int fz) {
for(int v:G[u]) if(v^fz) dfs1(v,u),cnt[u]+=cnt[v];
}
inline void dfs2(int u,int fz,int c) {
for(int v:G[u]) if(v^fz) {
if(!cnt[v]) ++deg[c],++deg[v],dfs2(v,u,v);
else dfs2(v,u,c);
}
}
inline int LCA(int u,int v) {
if(dep[u]<dep[v]) swap(u,v);
for(int k=19;~k;--k) if(dep[fa[u][k]]>=dep[v]) u=fa[u][k];
if(u==v) return u;
for(int k=19;~k;--k) if(fa[u][k]^fa[v][k]) u=fa[u][k],v=fa[v][k];
return fa[u][0];
}
signed main() {
int n,k;
scanf("%d%d",&n,&k);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
for(int i=1;i<=n;++i) scanf("%d",&col[i]),C[col[i]].push_back(i);
dfs0(1,0);
for(int i=1;i<=k;++i) {
for(int j=1;j<(int)C[i].size();++j) {
++cnt[C[i][0]],++cnt[C[i][j]],cnt[LCA(C[i][0],C[i][j])]-=2;
}
}
dfs1(1,0),dfs2(1,0,1);
int ans=0;
for(int i=1;i<=n;++i) if(!cnt[i]&°[i]==1) ++ans;
printf("%d\n",(ans+1)/2);
return 0;
}
L. Minerals
题目大意
交互器有
个元素,每个元素有颜色,每种颜色恰好两个元素。 你有一个集合
,你可以进行 次操作,每次操作会改变某个元素 在 中的状态,然后返回 中元素的颜色数。 请你分别求出每一对同色元素。
数据范围:
。
思路分析
考虑不断递归:每次维护两个集合
然后我们把
如果答案变大就放进
考虑如何求初始的
操作次数
考虑优化,注意到我们不关心每次的
操作次数
然后考虑避免清空
操作次数
接着考虑优化,注意到每次操作的代价是
考虑 dp 找出转移点,
打表观察到转移点具有单调性,可以二分加单调队列优化,但暴力从上一个决策点的位置转移已经足够通过此题。
操作次数
考虑接着优化,注意到我们求
此时转移变成
事实上,在
时间复杂度
代码呈现
#include<bits/stdc++.h>
#include"minerals.h"
using namespace std;
const int MAXN=43005;
int lst=0,cur,f[MAXN],p[MAXN];
inline bool qry(int x) {
cur=Query(x);
if(lst==cur) return false;
return lst=cur,true;
}
inline void solve(vector<int>&L,vector<int>&R,bool rv) {
int n=L.size();
if(n==1) return Answer(L[0],R[0]);
auto it=L.begin()+p[n];
vector <int> LX(L.begin(),it),LY(it,L.end()),RX,RY;
for(int i:LX) qry(i);
if(rv) swap(LX,LY);
for(int i=0;i<n-1;++i) (qry(R[i])?RY:RX).push_back(R[i]);
(RX.size()<LX.size()?RX:RY).push_back(R[n-1]);
solve(LX,RX,1),solve(LY,RY,0);
}
void Solve(int N) {
memset(f,0x3f,sizeof(f)),p[1]=1,f[1]=0;
for(int i=2;i<=N;++i) {
for(int j=p[i-1];j<=i/2;++j) {
int g=f[j]+f[i-j]+i+j-1;
if(g<f[i]) p[i]=j,f[i]=g;
}
}
vector <int> L,R;
for(int i=1;i<=2*N;++i) (qry(i)?L:R).push_back(i);
solve(L,R,1);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】