BalticOI 2022 题解

これ

Art Collections

对于每个元素,计算其在序列中的排名。
\(x\) 排在第 \(k\) 位,询问 \((x,1,...,x-1,x+1,...,n)=p,(1,...,x-1,x+1,...,n,x)=q\)
\(p=...+k-1,q=...+n-k\),所以 \(k=\frac{p-q+n+1}{2}\)
这样可以做到 \(2n\) 次询问。

考虑循环移位,可以做到 \(n\) 次询问。

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

#define pb push_back
const int N=4e3+100;

int ans[N],id[N];
vector<int> g,f;

void solve(int n){
	for(int i=1;i<=n;++i) g.pb(i);
	ans[1]=publish(g);
	for(int k=2;k<=n;++k){
		reverse(g.begin()+1,g.end()),reverse(g.begin(),g.end());
		ans[k]=publish(g);
	}
	for(int i=1;i<n;++i)
		id[i]=(ans[i]-ans[i+1]+n+1)/2;
	id[n]=(ans[n]-ans[1]+n+1)/2;
	f.resize(n);
	for(int i=1;i<=n;++i) f[id[i]-1]=i;
	answer(f);
}

Event Hopping

将区间用右端点代替,暴力建图跑最短路可以 \(O(n^2)\) 解决。

正难则反。
反过来看,每个点能到达的点是一段连续区间,这样跳的越远越好。
每个点贪心地跳到区间中 \(l\) 最小的点。
处理跳 \(k\) 次所能到达的最远点可以倍增。

\(O((n+q)logn)\) 解决了。

Uplifting Excursion

好题啊。

体积过大,考虑减小它。先求出一个体积在 \((l-m,l]\) 的最优方案。贪心,先将所有数加上,在删除最大/最小的。

考虑由该方案调整到答案。对于一个调整方案,一定可以通过改变它的调整顺序使得每次调整后的总体积在 \([l-m,l+m]\) 内,并且调整过程中不会有两个相同的体积,所以调整次数 \(<=2*m\)。那么体积最多加 \(m*m\)(因为最后体积最多为 \(m\),加的再多就减不回来了)。
对于反悔操作,类似网络流,加一个反向的物品即可。

二进制优化分组背包,时间复杂度 \(O(m^3log{(\sum a)})\)

真的不好写。。。

Flight to the Ford

通信啊(还没有翻译),跳了。

Stranded Far From Home

就是序列上的常见问题搬到了一般图上。
思考方式是一样的。

先找最大值,它一定可行。
将最大值删去,原图会分成若干个连通块。
求出每个连通块的总大小,与最大值相比。
小于最大值则不可行,大于最大值则递归下去。

有一个很精妙的实现。
可以将原图重建为树,其实就是将递归过程建出来,它肯定是树。
先尝试将权值小的点合并,每个连通块记录块中的最大值,充当该树(连通块肯定是一棵树)的根节点,连边时连根节点即可。
可以将点按权值从小到大排序,枚举出边并尝试向已经遍历过的点连边。
用并查集实现(详见代码),时间复杂度 \(O(nlogn)\)

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

using ll=long long;
#define pb push_back
const int N=2e5+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,m,a[N],id[N],fa[N],vis[N],ans[N]; ll siz[N];
vector<int> _g[N],g[N];
bool cmp(int x,int y){ return a[x]<a[y]; }

int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }
int mer(int x,int y){ x=find(x),y=find(y); if(x==y) return 0; return fa[y]=x,1; }

void dfs0(int u){ siz[u]=a[u]; for(int v:g[u]) dfs0(v),siz[u]+=siz[v]; }
void dfs1(int u){ ans[u]=1; for(int v:g[u]) if(siz[v]>=a[u]) dfs1(v); }

int main(){
	
	n=rd,m=rd;
	for(int i=1;i<=n;++i) id[i]=fa[i]=i,a[i]=rd;
	for(int i=1,x,y;i<=m;++i) x=rd,y=rd,_g[x].pb(y),_g[y].pb(x);
	
	sort(id+1,id+n+1,cmp);
	for(int i=1,u;i<=n;++i){
		u=id[i];
		for(int v:_g[u]){
			if(!vis[v]) continue; v=find(v);
			if(u!=v) fa[v]=u,g[u].pb(v);
		}
		vis[u]=1;
	}
	
	dfs0(id[n]),dfs1(id[n]);
	
	for(int i=1;i<=n;++i) printf("%d", ans[i]); puts("");
	
	return 0;
}

Boarding Passes

翻译的一坨。。。

简单说下题意。
有一排 \(n\) 个座位,\(A,B,...\)\(g\) 组乘客。每个乘客有一个固定的座位,需要从左边或右边走到对应的座位。
乘客按顺序出发。你可以决定组之间的顺序、每个乘客从左边或右边出发,但组内的顺序是等概率随机的。
求所有乘客经过已经入座的乘客的次数的最小值的期望。
求指正。

显然要 DP 求解。
决定组的顺序不太好直接做,可以状压。

每一组从左边或右边出发的方式,肯定是从中间分开,左边从左边出发,右边从右边出发。
任意两组之间的贡献可以 \(O(g^2n)\) 预处理。记 \(p\) 为分界点,该组间的贡献为 \(\frac{p(p-1)+(c-p)*(c-p-1)}{4}\)(对每一对点分别考虑)。
这样就可以 \(O(m)\) 求出在某一状态下以某点为分界点时该组对答案的贡献。
该贡献具有单峰性,可以三分求解最优分界点。

时间复杂度 \(O(g^2n+2^gm^2logn)\)

这里的三分写法很值得借鉴。
扩大可行解的范围,最后小范围枚举,可以去掉恶心的边界判断。

这里的小数最多有一个 \(0.5\),可以先让答案 \(*2\),最后再 \(/2\)

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

#define int long long
using db=double;
const int N=1e5+100,G=20,M=(1<<15)+100,INF=1e18;
inline int in(int S,int i){ return (S>>i)&1; }
#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=0,a[N],cost[G][G][N],cnt[G],f[M]; char s[N];

void init(char x,char y){
	for(int i=1;i<=n;++i) a[i]=(s[i]==x);
	for(int i=1;i<=n;++i) a[i]+=a[i-1];
	int pt=0;
	for(int i=1,las=0;i<=n;++i){
		if(s[i]!=y) continue;
		++pt,las+=a[i];
		cost[x-'A'][y-'A'][pt]+=las;
	}
	cnt[y-'A']=pt;
	for(int i=n,las=0;i;--i){
		if(s[i]!=y) continue;
		--pt,las+=a[n]-a[i-1];
		cost[x-'A'][y-'A'][pt]+=las;
	}
}

inline int calc(int S,int x,int p){ int res=0; for(int i=0;i<m;++i) if(in(S,i)) res+=cost[i][x][p]; return res*2+(p*(p-1)+(cnt[x]-p)*(cnt[x]-p-1))/2; }

signed main(){
	
	scanf("%s", s+1),n=strlen(s+1);
	for(int i=1;i<=n;++i) m=max(m,(int)s[i]-'A'+1);
	
	for(int i=0;i<m;++i)
		for(int j=0;j<m;++j)
			init(i+'A',j+'A');
	
	for(int S=1;S<(1<<m);++S) f[S]=INF;
	for(int S=0;S<(1<<m);++S){
		for(int i=0;i<m;++i){
			if(in(S,i)) continue;
			if(!cnt[i]){ f[S|(1<<i)]=min(f[S|(1<<i)],f[S]); continue; }
			int l=0,r=cnt[i];
			while(l+2<r){
				int mid=l+r>>1;
				if(calc(S,i,mid)<=calc(S,i,mid+1)) r=mid;
				else l=mid+1;
			}
			for(int p=l;p<=r;++p) f[S|(1<<i)]=min(f[S|(1<<i)],f[S]+calc(S,i,p));
		}
	}
	
	printf("%.4lf\n", (db)f[(1<<m)-1]/2.0);
	
	return 0;
} 
posted @ 2024-08-21 20:04  Idtwtei  阅读(21)  评论(0编辑  收藏  举报