JOISC2019游记

不定时更新(咕咕咕
UPD:3.28 Day1,2补完了
UPD:6.22 Day4被出成模拟赛了。。。所以补了上来

Day1

試験 (Examination)

Description

有n个学生,每个学生用二元组(Si,Ti)表示
有q组询问,每组询问给出三元组(Ai,Bi,Ci),问有多少个学生满足Si>=Ai,Ti>=Bi且Si+Ti>=Ci
n,q<=10^5

Solution

签到题,三维数点

Code

#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;
}

ナン (Naan)

Description

有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

Solution

预处理每个人将naan分成n段,每段愉悦值相等的切割点
然后每次贪心找当前最小的切割点切就好了
正确性显然

Code

#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;
}

ビーバーの会合 (Meetings)

Description

交互题
有一棵n个点的树,你每次可以询问(x,y,z),满足x!=y,x!=z,y!=z,交互库会告诉你距离x,y,z的距离和最小的点,保证这棵树每个点的度数<=18
用不超过40000次询问还原这棵树
n<=2000

Solution

考虑先确定一个点x,随机一个点y,问所有其他的点z
如果query(x,y,z)=z,那么说明z在x到y的路径上,否则我们知道z在哪个点的子树内,递归下去就好了
然后我们只需要将x到y路径上所有的点排序
然后就过了,有没有dalao可以证明一下啊QwQ

Code

#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);
}

Day2

ふたつのアンテナ (Two Antennas)

Description

有n个天线排成一排,相邻两个天线的距离为1,第i个天线可以向和其距离在[Ai,Bi]内的天线通信,高度为hi
有q次询问,每次询问[l,r]中可以互相通信的天线中高度差的绝对值的最大值是多少
n,q<=200000

Solution

离线维护扫描线,我们可以知道哪些天线可以和当前的天线i通信
那么可以互相通信的就是一个区间
显然绝对值可以拆开做两遍,考虑r为最大值的情况
考虑这样一个东西,如果天线i能和天线r通信,那么我们令ci=-hi,否则ci=-∞,那么我们就是要求hr-ci的最大值
这个东西是个历史最值直接套模板就好了
复杂度O(n log n)
感觉做麻烦了

Code

#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;
}

ふたつの料理 (Two Dishes)

Description

有两种菜,第一种菜分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

Solution

考虑最简单的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[i1][j]+P[i](SA[i]+SB[j]<=S[i]),F[i][j1]+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],然后修改就暴力就好了
注意必须先全部把修改加入再进行前缀最大值的推平操作

Code

#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;
}

ふたつの交通機関 (Two Transportations)

Description

这是一道通信题
有一张n个点带权连通图,每条边要么是小A的,要么是小B的
现在小A想知道点0到所有点的最短路,于是他选择和小B通信
两人只能发送不超过58000个0/1字符
n<=2000,wi<=500

Solution

考虑2000个点的最短路怎么做?
那当然是Dijkstra啦
我们发现我们只需要让小A和小B同时做Dij,然后每一步扩展取两人中能扩展的点中较小的那个
58000/n=29,一次扩展只能发29个bit
权值的话可以发最短路的差分值,只需要9个bit
但编号需要11个bit
我们发现我们刚好能发两个权值和一个编号
那么我们就让小A和小B分别先把权值发给对面,然后权值较小的那个把编号发过来
由于这题的通信格式比较麻烦所以需要精妙的实现

Code

Azer

#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;
}

Baijian

#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;
		}
	}
}

Day3

ランプ (Lamps)

Description

给出两个长度为n的0/1串S和T,你可以对S进行三种操作:区间赋值0/1和区间异或
求最少的步数使得S=T
n<=10^6

Solution

显然存在一种方法,使得所有的赋值区间不相交,所有的异或区间不相交
同时我们可以发现赋值一定在异或之前
那么直接设F[i][0…2]表示i这个位置是否被赋值,如果被赋值是多少
转移也很简单
复杂度O(n)

Code

#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;
}

Day 4

ケーキの貼り合わせ (Cake3)

Description

有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=1mVii=1mCiCi+1
求最大收益
n<=2e5

Solution

考虑减去的东西,显然可以做到2(max(Ci)-min(Ci))
将Ci排序,枚举区间[l,r],贡献为区间中V的前m大-2(Cr-Cl),求贡献最大的区间
每个r对应的l显然是单调的,直接决策单调性即可
复杂度O(n log^2 n)

Code

#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;
}

合併(Mergers)

Description

给出一棵树,每个点有颜色
一次操作定义为将所有颜色x改为颜色y
求最少的操作次数,使得对于树的每一条边的两段都存在颜色相同的点
n<=500000

Solution

对于每种颜色,其在树上的最小连通块一定合法,用并查集缩起来
现在树上没有相同颜色,考虑合并两个点,可以让一条路径上的边都合法
问题变成用最少的链覆盖这棵树,答案显然是叶子数/2

Code

#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&&deg[i]==1) ans++;
	printf("%d\n",ans+1>>1); 
	return 0;
}

鉱物(Minerals)

Description

有n种宝石,每种宝石都有两块
现在有一个黑盒子,你可以往里面扔一块宝石或者拿出一块宝石,每次操作后交互库会告诉你盒子中的宝石种类数
用不超过10^6次操作找到那些宝石是同一种的
n<=43000

Solution

先用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((1p)n)+(1+p)n,通过玄妙的分析可以发现p取 3 − 5 2 3-\sqrt 5\over 2 235 时最优
共需约980000次

Code

#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]]);
}

你可能感兴趣的:(心情,总结)