BalticOI 2024 题解

不如官解。

これ

Jobs

考虑原图为链的情况。将链砍成一段段,每一段都满足从开头开始拿,拿到段尾刚好利润大于 \(0\),记录一下需要的最少本金和产生利润。每次取可拿且需要本金最小的段,拿掉它,直到无段可拿则结束。用堆实现,时间复杂度 \(O(nlogn)\)

对于树的情况,类似地将树砍为一棵棵,并记录需要地本金与获得的利润,再类似贪心即可。难点在于怎么砍。

记以 \(u\) 为根需要的本金为 \(f_u\),获得的利润为 \(g_u\)。对于点 \(u\) 可以用最后的贪心方法来求,时间复杂度 \(O(n^2logn)\)

可以发现有一些点是会在它的祖先处被重复扩展的。可以考虑对于每个点开一个堆,计算完该点的答案后保留它。对于一个新点的计算,可以直接拿掉某个已经计算完成的树,并继承该点的堆,即合并一下。启发式合并,时间复杂度为 \(O(nlog^2n)\),可以通过。

Portal

对于 \(n\) 个点 \(P_1,P_2,...,P_n\),只有 \(n-1\) 个向量 \(P_1P_2,P_1P_3,...,P_1P_n\) 有用。
考虑两个向量的情况,即 \(n=3\)。若两个向量线性相关则解为无穷大,若线性无关则解为两个向量所形成的平行四边形的面积(下图来自官方题解)。

对于三个向量的情况,考虑将其减为两个向量的情况。为方便,记当前两个向量为 \((a,0),(p,q)\),加入的向量为 \((x,y)\)。若 \(y=0\) 则新向量为 \((gcd(a,x),0),(p,q)\)。否则可以通过辗转相除法用 \((p,q)\)\((x,y)\) 消为 \(y=0\) 的向量。可以证明这两种操作(即取 gcd 与向量相减)都不会产生错误答案。

对于多个向量,依次处理即可。

时间复杂度 \(O(nlogV)\)

注意开 int128

此题类似。

#include<bits/stdc++.h>
using namespace std;

#define int __int128
const int N=1e5+100;
#define gc getchar()
#define rd read()
inline int read(){
	int x=0,f=0; char c=gc;
	for(;c<'0'||c>'9';c=gc) f|=(c=='-');
	for(;c>='0'&&c<='9';c=gc) x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}

int n,x[N],y[N];

int a=0,p=0,q=0,k;
void sol(int x,int y){
	while(q) k=y/q,x-=k*p,y-=k*q,swap(x,p),swap(y,q);
	a=__gcd(a,p),p=x,q=y;
}

signed main(){

	n=rd;
	for(int i=1;i<=n;++i) x[i]=rd,y[i]=rd;
	
	for(int i=2;i<=n;++i) sol(x[i]-x[1],y[i]-y[1]);
	
	long long ans=a*q; if(ans<0) ans=-ans;
	printf("%lld\n", ans==0?-1:ans);
	
	return 0;
} 

Trains

挺套路的。
显然具有阶段性,可以 DP。
有一个 \(O(n^2)\) 暴力,就是模拟这个过程。
这种隔段跳可以考虑根号分治,就做完了,时间复杂度 \(O(n\sqrt n)\)

Fire

断环成链,每次需要回答形如覆盖区间 \([i,i+m]\) 的询问。
倍增,记 \(f_{x,i}\) 为从 \(x\) 开始,选择 \(2^i\) 个人,跳到的最远点。
时间复杂度 \(O(nlogn)\)

Tiles

把竖直线段拿出来,按横坐标奇偶分组,每次加入线段则区间异或 \(1\)

  • 若有奇偶不同的线段相交则无解。
  • 若有线段长为奇数则无解。
  • 若此时没有奇线段或没有偶线段则按 \(x\) 奇偶更新答案。

用 map 实现。

#include<bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
const int N=2e5+100,INF=1e9+7;
#define gc getchar()
#define rd read()
inline int read(){
	int x=0,f=0; char c=gc;
	for(;c<'0'||c>'9';c=gc) f|=(c=='-');
	for(;c>='0'&&c<='9';c=gc) x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}

int n,m,ans=0,over=0,X[N],Y[N];

int res[2];
vector<array<int,3>> vc;
map<int,int> L,P;

inline void ers(int l){ --res[P[l]],over-=(L[l]-l)&1,L.erase(l),P.erase(l); }
void ins(int l,int r,int p){
	over+=(r-l)&1,P[l]=p,++res[p];
	auto it=L.insert({l,r}).first; int z;
	if(prev(it)->se==l&&P[prev(it)->fi]==p) z=prev(it)->fi,ers(z),ers(l),ins(z,r,p);
	else if(next(it)->fi==r&&P[r]==p) z=next(it)->se,ers(r),ers(l),ins(l,z,p);
}
void spl(int l,int r,int x,int p){
	int y=L[x];
	if(r<=x||y<=l) return;
	if(P[x]!=p) printf("%d\n", ans),exit(0);
	ers(x);
	if(x<l) ins(x,l,p);
	if(r<y) ins(r,y,p);
}

int main(){
	
	n=rd,m=rd;
	for(int i=1;i<=n;++i) X[i]=rd,Y[i]=rd;
	X[0]=X[n],Y[0]=Y[n];
	for(int i=1;i<=n;++i) if(X[i]==X[i-1]) vc.pb({X[i],min(Y[i],Y[i-1]),max(Y[i],Y[i-1])});
	sort(vc.begin(),vc.end());
	
	L[INF]=INF,L[-INF]=-INF,P[INF]=P[-INF]=0;
	int las=0;
	for(auto [x,l,r]:vc){
		if(x!=las){
			if(over) return printf("%d\n", ans),0;
			if(!res[0]) ans=max(ans,x-!(x&1));
			if(!res[1]) ans=max(ans,x-(x&1));
		}
		auto it=L.upper_bound(l);
		if(prev(it)->se<=l&&r<=it->fi) ins(l,r,x&1);
		else while(it->se>=l) spl(l,r,(it--)->fi,x&1);
		las=x;
	}
	
	printf("%d\n", m);
	return 0;
} 

Flooding Wall

横着看,将 \(a,b\) 混在一起组成长为 \(2n\) 的数组 \(a\),并从大到小扫,每次计算 \([a[i-1],a[i]]\) 有水的地块数总数,乘 \(a[i]-a[i-1]\) 加入答案。
对于列 \(x\),先强制它的高小于等于 \(a[i-1]\)。正难则反,总方案好计算,减去 \(x\) 列上范围内无水的方案数。

  • \([1,x]\) 最大值小于等于 \(a[i-1]\)\([x,n]\) 最大值大于 \(a[i-1]\)
  • \([1,x]\) 最大值大于 \(a[i-1]\)\([x,n]\) 最大值小于等于 \(a[i-1]\)
  • \([1,x]\) 最大值小于等于 \(a[i-1]\)\([x,n]\) 最大值小于等于 \(a[i-1]\)

这时 \(x\) 列上范围内无水。
容斥一下,方案数为 \([1,x]\) 最大值小于等于 \(a[i-1]\) 的方案数 \(+\) \([x,n]\) 最大值小于等于 \(a[i-1]\) 的方案数 \(-\) \([1,n]\) 最大值小于等于 \(a[i-1]\) 的方案数。
三是好计算的。一与二类似。
直接计算是 \(O(n)\) 的。发现从大到小扫每次只会改变某一位置的值,可以动态维护。使用线段树维护,总时间复杂度 \(O(nlogn)\)

#include<bits/stdc++.h>
using namespace std;

#define pii pair<int,int>
#define fi first
#define se second
const int N=5e5+100,MOD=1e9+7;
inline void mod(int &x){ x+=x>=MOD?-MOD:x<0?MOD:0; }
#define gc getchar()
#define rd read()
inline int read(){
	int x=0,f=0; char c=gc;
	for(;c<'0'||c>'9';c=gc) f|=(c=='-');
	for(;c>='0'&&c<='9';c=gc) x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x; 
}

int pw[N];
void init(int n){ pw[0]=1; for(int i=1;i<=n;++i) pw[i]=2ll*pw[i-1]%MOD; }

int n,cnt[N]; pii a[N*2];

#define lc (id<<1)
#define rc (id<<1|1)
#define mid (l+r>>1)
struct TREE{ int fl,fr,mul; }t[N<<3];
void pushup(int id){
	t[id].mul=1ll*t[lc].mul*t[rc].mul%MOD;
	mod(t[id].fl=t[lc].fl+1ll*t[lc].mul*t[rc].fl%MOD);
	mod(t[id].fr=t[rc].fr+1ll*t[rc].mul*t[lc].fr%MOD);	
}
void bui(int id,int l,int r){
	if(l==r) return t[id]={1ll*cnt[l]*pw[n-l]%MOD,1ll*cnt[l]*pw[l-1]%MOD,cnt[l]},void();
	bui(lc,l,mid),bui(rc,mid+1,r),pushup(id);
}
void chg(int id,int l,int r,int ql){
	if(l==r) return t[id]={1ll*cnt[l]*pw[n-l]%MOD,1ll*cnt[l]*pw[l-1]%MOD,cnt[l]},void();
	ql<=mid?chg(lc,l,mid,ql):chg(rc,mid+1,r,ql); pushup(id);
}

int main(){
	
	n=rd,init(n);
	for(int i=1;i<=n;++i) a[i]={rd,i};
	for(int i=1;i<=n;++i) a[i+n]={rd,i};
	
	for(int i=1;i<=n;++i) cnt[i]=2; bui(1,1,n);
	
	int ans=0;
	sort(a+1,a+2*n+1);
	for(int i=2*n;i>=2;--i){
		--cnt[a[i].se],chg(1,1,n,a[i].se);
		if(a[i].fi!=a[i-1].fi) mod(ans+=1ll*(1ll*(i-1)*pw[n-1]%MOD-t[1].fl-t[1].fr+1ll*n*t[1].mul%MOD)%MOD*(a[i].fi-a[i-1].fi)%MOD);
	}
	printf("%d\n", ans);
	
	return 0;
} 
posted @ 2024-08-15 16:25  Idtwtei  阅读(36)  评论(0编辑  收藏  举报