不定时更新(咕咕咕
UPD:3.28 Day1,2补完了
UPD:6.22 Day4被出成模拟赛了。。。所以补了上来
有n个学生,每个学生用二元组(Si,Ti)表示
有q组询问,每组询问给出三元组(Ai,Bi,Ci),问有多少个学生满足Si>=Ai,Ti>=Bi且Si+Ti>=Ci
n,q<=10^5
签到题,三维数点
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
const int N=2e5+5;
int n,m,a[N],an[N],w;
struct P{int x,y,z,id;}p[N];
bool cmp(P a,P b) {
if (a.x!=b.x) return a.x>b.x;
if (a.y!=b.y) return a.y>b.y;
if (a.z!=b.z) return a.z<b.z;
return a.id<b.id;
}
bool cmp1(P a,P b) {return a.y>b.y;}
int tr[N];
void I(int x) {for(;x<=w;x+=x&-x) tr[x]++;}
int Q(int x) {int ret=0;for(;x;x-=x&-x) ret+=tr[x];return ret;}
void D(int x) {for(;x<=w;x+=x&-x) tr[x]=0;}
void solve(int l,int r) {
if (l==r) return;
int mid=l+r>>1;
solve(l,mid);solve(mid+1,r);
int j=l-1;
fo(i,mid+1,r) {
while (j<mid&&p[j+1].y>=p[i].y) {j++;if (!p[j].id) I(p[j].z);}
if (p[i].id) {
an[p[i].id]+=Q(p[i].z);
p[i].id=p[i].id;
}
}
fo(i,l,mid) if (!p[i].id) D(p[i].z);
sort(p+l,p+r+1,cmp1);
}
int main() {
n=read();m=read();
fo(i,1,n) p[i].x=read(),p[i].y=read(),p[i].z=p[i].x+p[i].y;
fo(i,1,m) p[i+n].x=read(),p[i+n].y=read(),p[i+n].z=read(),p[i+n].id=i;
fo(i,1,n+m) a[++w]=p[i].z;
sort(a+1,a+w+1);w=unique(a+1,a+w+1)-a-1;
fo(i,1,n+m) p[i].z=w-(lower_bound(a+1,a+w+1,p[i].z)-a)+1;
sort(p+1,p+n+m+1,cmp);solve(1,n+m);
fo(i,1,m) printf("%d\n",an[i]);
return 0;
}
有n个人在分一块naan(印度薄饼?),naan可以被分为L段长度为1的小块
这n个人打算在naan上切n-1刀,分成n段分别拿走
第i个人拿长度为1的第j段会获得V[i][j]的愉悦值
你需要给出一种方案,使得第i个人最终获得的愉悦值>= ∑ V i , j L \sum{V_{i,j}\over L} ∑LVi,j
n<=2000,L<=2000
预处理每个人将naan分成n段,每段愉悦值相等的切割点
然后每次贪心找当前最小的切割点切就好了
正确性显然
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define mp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pr;
const int N=2e3+5;
int n,L,v[N][N],id[N];
pr p[N][N],an[N];
bool vis[N];
bool small(pr a,pr b) {return (long double)a.first*b.second<(long double)b.first*a.second;}
int main() {
scanf("%d%d",&n,&L);
fo(i,1,n) fo(j,1,L) scanf("%d",&v[i][j]);
fo(i,1,n) {
int sum=0,now=0;
fo(j,1,L) sum+=v[i][j];
int k=1;
fo(j,1,n) {
while (k<L&&(ll)(now+v[i][k])*n<(ll)sum*j) now+=v[i][k++];
ll a=(ll)(k-1)*n*v[i][k]+(ll)sum*j-(ll)now*n;
ll b=(ll)n*v[i][k];ll d=__gcd(a,b);
p[i][j]=mp(a/d,b/d);
}
}
fo(i,1,n) {
an[i]=mp(1,0);id[i]=0;
fo(j,1,n) if (!vis[j]&&small(p[j][i],an[i])) an[i]=p[j][i],id[i]=j;
vis[id[i]]=1;
}
fo(i,1,n-1) printf("%lld %lld\n",an[i].first,an[i].second);
fo(i,1,n) printf("%d ",id[i]);
return 0;
}
交互题
有一棵n个点的树,你每次可以询问(x,y,z),满足x!=y,x!=z,y!=z,交互库会告诉你距离x,y,z的距离和最小的点,保证这棵树每个点的度数<=18
用不超过40000次询问还原这棵树
n<=2000
考虑先确定一个点x,随机一个点y,问所有其他的点z
如果query(x,y,z)=z,那么说明z在x到y的路径上,否则我们知道z在哪个点的子树内,递归下去就好了
然后我们只需要将x到y路径上所有的点排序
然后就过了,有没有dalao可以证明一下啊QwQ
#include "meetings.h"
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define pb(a) push_back(a)
using namespace std;
typedef vector<int> vec;
typedef unsigned long long ull;
ull seed;
int ran(int l,int r) {
seed^=seed<<15;
seed^=seed>>13;
seed^=seed<<5;
return seed%(r-l+1)+l;
}
const int N=2e3+5;
int n,rt;
bool cmp1(int x,int y) {return Query(rt,x,y)==x;}
vec tmp;
void calc(int x,vec p) {
int y=p[ran(0,p.size()-1)];
vector<vec> q(n);vec t;t.pb(y);
for(int z:p) {
if (z==y) continue;
int w=Query(x,y,z);
if (w==z) t.pb(z);
else q[w].pb(z);
}
if (q[x].size()) calc(x,q[x]);
for(int z:t) if (q[z].size()) calc(z,q[z]);
rt=x;sort(t.begin(),t.end(),cmp1);
for(int i=0;i<t.size();i++) {
int x=i?t[i-1]:rt,y=t[i];
if (x>y) swap(x,y);
Bridge(x,y);
}
}
void Solve(int N) {
n=N;fo(i,1,n-1) tmp.pb(i);seed=233333*666666;
calc(0,tmp);
}
有n个天线排成一排,相邻两个天线的距离为1,第i个天线可以向和其距离在[Ai,Bi]内的天线通信,高度为hi
有q次询问,每次询问[l,r]中可以互相通信的天线中高度差的绝对值的最大值是多少
n,q<=200000
离线维护扫描线,我们可以知道哪些天线可以和当前的天线i通信
那么可以互相通信的就是一个区间
显然绝对值可以拆开做两遍,考虑r为最大值的情况
考虑这样一个东西,如果天线i能和天线r通信,那么我们令ci=-hi,否则ci=-∞,那么我们就是要求hr-ci的最大值
这个东西是个历史最值直接套模板就好了
复杂度O(n log n)
感觉做麻烦了
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
const int N=5e5+5,inf=0x7fffffff;
struct At{int h,a,b;}p[N];
int n,q,an[N];
struct Que{int l,r,id;}ask[N];
bool cmpr(Que a,Que b) {return a.r<b.r;}
bool cmpl(Que a,Que b) {return a.l>b.l;}
vector<int> in[N],out[N];
#define pb(a) push_back(a)
struct Seg{ll mx,lmx,smx,cur,mcur;}tr[N<<2];
void update(int v) {
tr[v].smx=tr[v].mx=max(tr[v<<1].mx,tr[v<<1|1].mx);
tr[v].lmx=max(tr[v].lmx,tr[v].mx);
}
void build(int v,int l,int r) {
if (l==r) {
tr[v].smx=tr[v].mx=tr[v].lmx=-inf;
tr[v].cur=tr[v].mcur=0;
return;
}
tr[v].lmx=-inf;tr[v].cur=tr[v].mcur=0;
int mid=l+r>>1;
build(v<<1,l,mid);build(v<<1|1,mid+1,r);
update(v);
}
void Down(int v) {
if (tr[v].cur||tr[v].mcur) {
int ls=v<<1,rs=v<<1|1;
tr[ls].mcur=max(tr[ls].mcur,tr[ls].cur+tr[v].mcur);
tr[ls].cur+=tr[v].cur;
tr[ls].lmx=max(tr[ls].lmx,tr[ls].mcur+tr[ls].smx);
tr[ls].mx+=tr[v].cur;
tr[rs].mcur=max(tr[rs].mcur,tr[rs].cur+tr[v].mcur);
tr[rs].cur+=tr[v].cur;
tr[rs].lmx=max(tr[rs].lmx,tr[rs].mcur+tr[rs].smx);
tr[rs].mx+=tr[v].cur;
tr[v].cur=tr[v].mcur=0;
}
}
void Modify(int v,int l,int r,int x,int y,int z) {
if (x>y) return;
if (x<=l&&r<=y) {
tr[v].cur+=z;tr[v].mcur=max(tr[v].mcur,tr[v].cur);
tr[v].lmx=max(tr[v].lmx,tr[v].mcur+tr[v].smx);tr[v].mx+=z;
return;
}
int mid=l+r>>1;Down(v);
if (x<=mid) Modify(v<<1,l,mid,x,y,z);
if (y>mid) Modify(v<<1|1,mid+1,r,x,y,z);
update(v);
}
int Query(int v,int l,int r,int x,int y) {
if (x<=l&&r<=y) return tr[v].lmx;
int mid=l+r>>1,mx=-inf;Down(v);
if (x<=mid) mx=max(mx,Query(v<<1,l,mid,x,y));
if (y>mid) mx=max(mx,Query(v<<1|1,mid+1,r,x,y));
update(v);
return mx;
}
void solve_r() {
fo(i,1,n) in[i].clear(),out[i].clear();
fo(i,1,n) in[p[i].a+i].pb(i),out[p[i].b+i].pb(i);
sort(ask+1,ask+q+1,cmpr);int k=0;
build(1,1,n);
fo(i,1,n) {
for(auto j:in[i]) Modify(1,1,n,j,j,inf-p[j].h);
int l=i-p[i].b,r=i-p[i].a;
Modify(1,1,n,max(l,1),r,p[i].h);
while (k<q&&ask[k+1].r==i) k++,an[ask[k].id]=max(an[ask[k].id],Query(1,1,n,ask[k].l,ask[k].r));
Modify(1,1,n,max(l,1),r,-p[i].h);
for(auto j:out[i]) Modify(1,1,n,j,j,p[j].h-inf);
}
}
void solve_l() {
fo(i,1,n) in[i].clear(),out[i].clear();
fo(i,1,n) {
int l=i-p[i].b,r=i-p[i].a;l=max(l,1);
if (l>r) continue;
in[r].pb(i);out[l].pb(i);
}
sort(ask+1,ask+q+1,cmpl);int k=0;
build(1,1,n);
fd(i,n,1) {
for(auto j:in[i]) Modify(1,1,n,j,j,inf-p[j].h);
int l=i+p[i].a,r=i+p[i].b;
Modify(1,1,n,l,min(r,n),p[i].h);
while (k<q&&ask[k+1].l==i) k++,an[ask[k].id]=max(an[ask[k].id],Query(1,1,n,ask[k].l,ask[k].r));
Modify(1,1,n,l,min(r,n),-p[i].h);
for(auto j:out[i]) Modify(1,1,n,j,j,p[j].h-inf);
}
}
int main() {
n=read();
fo(i,1,n) p[i].h=read(),p[i].a=read(),p[i].b=read();
q=read();
fo(i,1,q) ask[i].l=read(),ask[i].r=read(),ask[i].id=i,an[i]=-1;
solve_r();solve_l();
fo(i,1,q) printf("%d\n",an[i]);
return 0;
}
有两种菜,第一种菜分n个部分,第i个部分有(Ai,Si,Pi),表示做这个部分需要Ai的时间,如果你在Si时刻之前做完了你会获得Pi的价值,必须要做完i-1这个部分才能做i这个部分
第二种菜分m个部分,第i个部分有(Bi,Ti,Qi),表示做这个部分需要Bi的时间,如果你在Ti时刻之前做完了你会获得Qi的价值,必须要做完i-1这个部分才能做i这个部分
注意Pi和Qi可以是负数,你开始之后不能休息,能拿的价值必须拿,必须做完所有n+m个部分
问你最多能获得多少的价值
n,m<=10^6
考虑最简单的nmDp,设SA[i]表示Ai的前缀和,SB[i]表示Bi的前缀和
设F[i][j]表示已经做完前i部分的第一种菜,和前j部分的第二种菜的最大收益,我们有
F [ i ] [ j ] = max ( F [ i − 1 ] [ j ] + P [ i ] ∗ ( S A [ i ] + S B [ j ] < = S [ i ] ) , F [ i ] [ j − 1 ] + Q [ j ] ∗ ( S A [ i ] + S B [ j ] < = T [ j ] ) ) F[i][j]=\max (F[i-1][j]+P[i]*(SA[i]+SB[j]<=S[i]),F[i][j-1]+Q[j]*(SA[i]+SB[j]<=T[j])) F[i][j]=max(F[i−1][j]+P[i]∗(SA[i]+SB[j]<=S[i]),F[i][j−1]+Q[j]∗(SA[i]+SB[j]<=T[j]))
考虑按i的顺序维护所有的F[j]
第一种转移相当于把F[j]的一段前缀加上P[i]
第二种转移相当于把F[j]和F[j-1]+Q[j]取max
注意这里的Q[j]要么是0,要么是原来的Q[j],且每个Q[j]只会变化一次
我们可以维护F[j]的差分,设为D[j]
那么区间加变成了单点减,第二种转移相当于是将D[j]和Q[j]取max,注意因为是差分值所有如果D[j]修改了D[j+1]也会修改
具体的,如果D[j] 我们不妨维护所有的D[j]-Q[j]=W[j],那么变成了,如果W[j]<0,W[j+1]+=W[j],W[j]=0,且对W[j]的修改只有单点修改
容易发现对于所有W[j]=0的W[j]都是没用的,所以我们可以用一个set来维护所有不为0的W[j],然后修改就暴力就好了
注意必须先全部把修改加入再进行前缀最大值的推平操作
#include
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define mp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
ll read() {
char ch;int sig=1;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar()) if (ch=='-') sig=-1;
ll x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*sig;
}
const int N=1e6+5;
int n,m,p[N],q[N],tot;
ll sa[N],sb[N],s[N],t[N],d[N],w[N];
int pa[N],pb[N];
vector<int> vec[N],P;
set<int> S;
typedef set<int> :: iterator it;
it id[N];
void solve(int x) {
it pos=S.lower_bound(x);tot=0;
ll v=0;
while (pos!=S.end()) {
d[*pos]+=v;
if (d[*pos]>=0) break;
v=d[*pos];d[*pos]=0;
id[++tot]=pos++;
}
fo(i,1,tot) S.erase(id[i]);
}
int main() {
n=read();m=read();
fo(i,1,n) sa[i]=sa[i-1]+read(),s[i]=read(),p[i]=read();
fo(i,1,m) sb[i]=sb[i-1]+read(),t[i]=read(),q[i]=read();
fo(i,1,n) pa[i]=upper_bound(sb,sb+m+1,s[i]-sa[i])-sb-1;
fo(i,1,m) pb[i]=upper_bound(sa,sa+n+1,t[i]-sb[i])-sa-1;
ll ans=0;
fo(i,1,m) {
if (pb[i]>=0) {
w[i]=q[i];
vec[pb[i]+1].push_back(i);
}
}
fo(i,1,n) {
P.clear();
for(int z:vec[i]) d[z]+=w[z],S.insert(z),P.push_back(z),w[z]=0;
if (pa[i]>=0) {
ans+=p[i];
if (pa[i]+1<=m) d[pa[i]+1]-=p[i],S.insert(pa[i]+1),P.push_back(pa[i]+1);
}
sort(P.begin(),P.end());
for(int z:P) solve(z);
}
fo(i,1,m) ans+=d[i]+w[i];
printf("%lld\n",ans);
return 0;
}
这是一道通信题
有一张n个点带权连通图,每条边要么是小A的,要么是小B的
现在小A想知道点0到所有点的最短路,于是他选择和小B通信
两人只能发送不超过58000个0/1字符
n<=2000,wi<=500
考虑2000个点的最短路怎么做?
那当然是Dijkstra啦
我们发现我们只需要让小A和小B同时做Dij,然后每一步扩展取两人中能扩展的点中较小的那个
58000/n=29,一次扩展只能发29个bit
权值的话可以发最短路的差分值,只需要9个bit
但编号需要11个bit
我们发现我们刚好能发两个权值和一个编号
那么我们就让小A和小B分别先把权值发给对面,然后权值较小的那个把编号发过来
由于这题的通信格式比较麻烦所以需要精妙的实现
#include "Azer.h"
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=lst[a];i;i=nxt[i])
#define mp(a,b) make_pair(a,b)
using namespace std;
const int N=2e3+5,M=5e5+5,inf=0x7fffffff;
namespace {
int n,m,dis[N],la,cnt,val,id,tot;
int t[M<<1],nxt[M<<1],v[M<<1],lst[N],l;
bool vis[N];
void add(int x,int y,int z) {t[++l]=y;v[l]=z;nxt[l]=lst[x];lst[x]=l;}
pair<int,int> find() {
pair<int,int> ret=mp(inf,0);
fo(i,0,n-1) if (!vis[i]) ret=min(ret,mp(dis[i],i));
if (ret.first!=inf) ret.first-=la;
else ret.first=(1<<10)-1;
return ret;
}
void update(int x,int w) {
dis[x]=la=w;vis[x]=1;
rep(i,x) dis[t[i]]=min(dis[t[i]],dis[x]+v[i]);
}
}
void InitA(int N,int A,vector<int> U,vector<int> V,vector<int> C) {
n=N;m=A;
fo(i,0,n-1) dis[i]=inf;
fo(i,0,m-1) {
add(U[i],V[i],C[i]);
add(V[i],U[i],C[i]);
}
update(0,0);cnt=-1;
}
void ReceiveA(bool x) {
do {
if (tot==n-1) return;
if (cnt==-1) {
pair<int,int> ret=find();
fo(i,0,8) SendA(ret.first>>i&1);
cnt=0;
} else if (cnt<9) {
val|=(1<<cnt)*x;cnt++;
if (cnt==9) {
pair<int,int> ret=find();
if (val>=ret.first) {
fo(i,0,10) SendA(ret.second>>i&1);
update(ret.second,ret.first+la);
val=0;cnt=-1;tot++;
} else cnt++;
}
} else {
id|=(1<<cnt-10)*x;cnt++;
if (cnt==21) {
update(id,val+la);
val=id=0;cnt=-1;tot++;
}
}
} while (cnt==-1);
}
vector<int> Answer() {
vector<int> an(n);
fo(i,0,n-1) an[i]=dis[i];
return an;
}
#include "Baijan.h"
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=lst[a];i;i=nxt[i])
#define mp(a,b) make_pair(a,b)
using namespace std;
const int N=2e3+5,M=5e5+5,inf=0x7fffffff;
namespace {
int n,m,dis[N],la,cnt,val,id;
int t[M<<1],nxt[M<<1],v[M<<1],lst[N],l;
bool vis[N];
void add(int x,int y,int z) {t[++l]=y;v[l]=z;nxt[l]=lst[x];lst[x]=l;}
pair<int,int> find() {
pair<int,int> ret=mp(inf,0);
fo(i,0,n-1) if (!vis[i]) ret=min(ret,mp(dis[i],i));
if (ret.first!=inf) ret.first-=la;
else ret.first=(1<<10)-1;
return ret;
}
void update(int x,int w) {
dis[x]=la=w;vis[x]=1;
rep(i,x) dis[t[i]]=min(dis[t[i]],dis[x]+v[i]);
}
}
void InitB(int N,int B,vector<int> S,vector<int> T,vector<int> D) {
n=N;m=B;
fo(i,0,n-1) dis[i]=inf;
fo(i,0,m-1) {
add(S[i],T[i],D[i]);
add(T[i],S[i],D[i]);
}
update(0,0);SendB(1);
}
void ReceiveB(bool y) {
if (cnt<9) {
val|=(1<<cnt)*y;cnt++;
if (cnt==9) {
pair<int,int> ret=find();
fo(i,0,8) SendB(ret.first>>i&1);
if (val>ret.first) {
fo(i,0,10) SendB(ret.second>>i&1);
update(ret.second,ret.first+la);val=cnt=0;
} else ++cnt;
}
} else {
id|=(1<<cnt-10)*y;cnt++;
if (cnt==21) {
update(id,val+la);
val=cnt=id=0;
}
}
}
给出两个长度为n的0/1串S和T,你可以对S进行三种操作:区间赋值0/1和区间异或
求最少的步数使得S=T
n<=10^6
显然存在一种方法,使得所有的赋值区间不相交,所有的异或区间不相交
同时我们可以发现赋值一定在异或之前
那么直接设F[i][0…2]表示i这个位置是否被赋值,如果被赋值是多少
转移也很简单
复杂度O(n)
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e6+5;
int n,f[N][3],ans;
char s[N],t[N];
bool ok(int tp,int x) {
if (!x) return 1;
return t[x]-'0'==(tp?tp-1:s[x]-'0');
}
int main() {
scanf("%d",&n);
scanf("%s",s+1);scanf("%s",t+1);
f[0][0]=0;
fo(i,1,n) fo(j,0,2) f[i][j]=n;
fo(i,1,n)
fo(j,0,2)
fo(k,0,2) {
int tmp=f[i-1][j];
if (j&&j!=k) tmp++;
if (ok(k,i)&&!ok(j,i-1)) tmp++;
f[i][k]=min(f[i][k],tmp);
}
int ans=n;
fo(i,0,2) ans=min(ans,f[n][i]+(i>0)+!ok(i,n));
printf("%d\n",ans);
return 0;
}
有n块蛋糕,第i块有Ci和Vi
你需要从中选出m块并排成一个圆,你的收益为 ∑ i = 1 m V i − ∑ i = 1 m ∣ C i − C i + 1 ∣ \sum_{i=1}^{m}V_i{}-\sum_{i=1}^{m}|C_{i}-C_{i+1}| ∑i=1mVi−∑i=1m∣Ci−Ci+1∣
求最大收益
n<=2e5
考虑减去的东西,显然可以做到2(max(Ci)-min(Ci))
将Ci排序,枚举区间[l,r],贡献为区间中V的前m大-2(Cr-Cl),求贡献最大的区间
每个r对应的l显然是单调的,直接决策单调性即可
复杂度O(n log^2 n)
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
const int N=2e5+5;
const ll inf=1e16;
int n,m,c[N],len;
pair<int,int> a[N];
ll ans;
int ls[N<<5],rs[N<<5],sz[N<<5],rt[N],tot;
ll sum[N<<5];
void modify(int &v,int l,int r,int x) {
sum[++tot]=sum[v];sz[tot]=sz[v];ls[tot]=ls[v];rs[tot]=rs[v];
v=tot;sum[v]+=c[x];sz[v]++;
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) modify(ls[v],l,mid,x);
else modify(rs[v],mid+1,r,x);
}
ll query(int v1,int v2,int l,int r,int k) {
if (l==r) return (ll)c[l]*min(k,sz[v1]-sz[v2]);
int mid=l+r>>1;
if (k<=sz[rs[v1]]-sz[rs[v2]]) return query(rs[v1],rs[v2],mid+1,r,k);
else return sum[rs[v1]]-sum[rs[v2]]+query(ls[v1],ls[v2],l,mid,k-(sz[rs[v1]]-sz[rs[v2]]));
}
void solve(int l,int r,int L,int R) {
int mid=l+r>>1,MID;ll ret=-inf;
fo(i,max(L,mid+m-1),R) {
ll tmp=query(rt[i],rt[mid-1],1,len,m)-(a[i].first-a[mid].first)*2;
if (tmp>ret) ret=tmp,MID=i;
}
ans=max(ans,ret);
if (l<mid) solve(l,mid-1,L,MID);
if (mid<r) solve(mid+1,r,MID,R);
}
int main() {
freopen("cake3.in","r",stdin);
freopen("cake3.out","w",stdout);
n=read();m=read();
fo(i,1,n) a[i].second=c[i]=read(),a[i].first=read();
sort(a+1,a+n+1);sort(c+1,c+n+1);len=unique(c+1,c+n+1)-c-1;
fo(i,1,n) {
rt[i]=rt[i-1];
int v=lower_bound(c+1,c+len+1,a[i].second)-c;
modify(rt[i],1,len,v);
}
ans=-inf;
solve(1,n-m+1,m,n);
printf("%lld\n",ans);
return 0;
}
给出一棵树,每个点有颜色
一次操作定义为将所有颜色x改为颜色y
求最少的操作次数,使得对于树的每一条边的两段都存在颜色相同的点
n<=500000
对于每种颜色,其在树上的最小连通块一定合法,用并查集缩起来
现在树上没有相同颜色,考虑合并两个点,可以让一条路径上的边都合法
问题变成用最少的链覆盖这棵树,答案显然是叶子数/2
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=lst[a];i;i=nxt[i])
using namespace std;
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
const int N=5e5+5;
int t[N<<1],nxt[N<<1],lst[N],l;
void add(int x,int y) {t[++l]=y;nxt[l]=lst[x];lst[x]=l;}
int n,m,fa[N],dep[N],f[N],deg[N];
vector<int> vec[N];
void dfs(int x,int y) {
fa[x]=y;dep[x]=dep[y]+1;
rep(i,x) if (t[i]!=y) dfs(t[i],x);
}
int get(int x) {return f[x]?f[x]=get(f[x]):x;}
void merge(int x,int y) {
x=get(x);y=get(y);
while (x!=y)
if (dep[x]>dep[y]) f[x]=fa[x],x=get(x);
else f[y]=fa[y],y=get(y);
}
int main() {
freopen("mergers.in","r",stdin);
freopen("mergers.out","w",stdout);
n=read();m=read();
fo(i,1,n-1) {
int x=read(),y=read();
add(x,y);add(y,x);
}
dfs(1,0);
fo(i,1,n) vec[read()].push_back(i);
fo(i,1,m) for(int j=0;j<vec[i].size();j++) merge(vec[i][j],vec[i][0]);
fo(i,1,n) rep(j,i) if (get(i)!=get(t[j])) ++deg[get(i)];
int ans=0;
fo(i,1,n) if (get(i)==i&°[i]==1) ans++;
printf("%d\n",ans+1>>1);
return 0;
}
有n种宝石,每种宝石都有两块
现在有一个黑盒子,你可以往里面扔一块宝石或者拿出一块宝石,每次操作后交互库会告诉你盒子中的宝石种类数
用不超过10^6次操作找到那些宝石是同一种的
n<=43000
先用2n次,依次加入,可以把2n块划分成两个集合,其中同种集合中的宝石互不相同
考虑分治,取A集合的一半放入盒子,询问所有B集合的宝石,就知道那些要到左区间,那些要到右区间
注意到我们操作次数其实是 F ( n ) = F ( p n ) + F ( ( 1 − p ) n ) + ( 1 + p ) n F(n)=F(pn)+F((1-p)n)+(1+p)n F(n)=F(pn)+F((1−p)n)+(1+p)n,通过玄妙的分析可以发现p取 3 − 5 2 3-\sqrt 5\over 2 23−5时最优
共需约980000次
#include "minerals.h"
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define pb(a) push_back(a)
using namespace std;
typedef vector<int> vec;
const int N=1e5+5;
vec a,b;
int n,c[N];
void work(vec a,vec b,int tp) {
if (a.size()==0) return;
if (a.size()==1) {c[a[0]]=b[0];c[b[0]]=a[0];return;}
int mid=(3-sqrt(5))/2*a.size();
if (tp==0) mid=a.size()-mid;
if (mid==0) mid++;
if (mid==a.size()) mid--;
vec a1,a2,b1,b2;
int la=0;
fo(i,0,mid-1) a1.pb(a[i]);
fo(i,mid,a.size()-1) a2.pb(a[i]);
if (tp==1) {
fo(i,0,mid-1) la=Query(a[i]);
fo(i,0,b.size()-1) {
if (a1.size()==b1.size()) b2.pb(b[i]);
else if (a2.size()==b2.size()) b1.pb(b[i]);
else {
int tmp=Query(b[i]);
if (tmp!=la) b1.pb(b[i]);
else b2.pb(b[i]);
la=tmp;
}
}
} else {
fo(i,mid,a.size()-1) la=Query(a[i]);
fo(i,0,b.size()-1) {
if (a1.size()==b1.size()) b2.pb(b[i]);
else if (a2.size()==b2.size()) b1.pb(b[i]);
else {
int tmp=Query(b[i]);
if (tmp!=la) b1.pb(b[i]);
else b2.pb(b[i]);
la=tmp;
}
}
}
work(a1,b1,0);work(a2,b2,1);
}
void Solve(int N) {
n=N;int la=0;
fo(i,1,2*n) {
int tmp=Query(i);
if (tmp>la) a.pb(i);
else b.pb(i);
la=tmp;
}
random_shuffle(a.begin(),a.end());
random_shuffle(b.begin(),b.end());
work(a,b,1);
fo(i,0,n-1) Answer(a[i],c[a[i]]);
}