COCI 17/18 Contest 6 Vrtić

傻逼题。

首先经典的结论是有很多个环,让每个环最小。

显然将数组从小到大排序,然后每个环都是取连续一段一定最优,交换法容易证明。

然后对于每个环内如何最优呢?假设我们有从小到大排序的数组 \(a_{\{1,n\}}\) ,最优一定是这样的:

\[a_1,a_3,a_5,a_7... a_8,a_6,a_4,a_2 \]

就是左右填数。为什么呢?考场是打表得出。因为构造出这个方法后使用交换证明发现是最优的。

这不是这道题最傻逼的地方,前面是平凡的思维。然后不知道傻逼出题人是怎么把这个环和这个结论复合在一起的。如果往二分答案想就失败了,因为现在问题等价于把一个长度为 \(n\) 的序列分为若干段,每段价值已经处理好(\(O(n^3)\) 预处理),段的长度已经固定(环长),这个东西贪心做不了,只能使用指数级别的算法。

如果长度为 \(i\) 的环有 \(x_i\) 个,我们得到以下式子:

\[(\sum_{i=1}^n x_i\times i)=n \]

然后,状态数就是:

\[\prod_{i=1}^n(x_i+1) \]

这个东西最大值是 \(4230144\),打表发现,取 \(x=\{16,8,5,3,3,2,2,1,1,1,1,1\}\) 得到。

由于转移还有常数,所以一个 \(\log\) 都不能带,所以你要建出一个 DAG,然后跑 dp

\(w=59,222,016\),时间复杂度是 \(O(w+n^3)\) 的。

给出 code。

#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
const int N=155,M=4230154;
using namespace std;
const int mod=1e9+7;
int n;
ll w[N],b[N];
int need[N];
ll solve(ll *a,int n)
{
	for(int i=1;i<=n;i++)
		if(i&1) b[i/2+1]=a[i];
		else b[n-i/2+1]=a[i];
	ll maxx=0;
	b[0]=b[n];
	for(int i=1;i<=n;i++) maxx=max(maxx,abs(b[i]-b[i-1]));
	return maxx;
}
ll g[N][N];
int p[N],siz[N],pre[N];
int cir[N],len;
bool vis[N];
ll f[M];
int from[M];
vector<int>E[N];
void decode(int x,int *cur)
{
	for(int i=len;i>=1;i--)
	{
		cur[i]=x/pre[i];
		x%=pre[i];
	}
}
int cur[N],res[N];
int main()
{
//	freopen("apples.in","r",stdin); 
//	freopen("apples.out","w",stdout); 
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),p[i]=x;
	for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
	sort(w+1,w+1+n);
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
			g[i][j]=solve(w+i-1,j-i+1);
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		int num=1,now=p[i];
		while(now^i) vis[now]=1,now=p[now],++num;
		siz[num]++;
		E[num].pb(i);
	}
	for(int i=1;i<=n;i++)
		if(siz[i]) cir[++len]=i,siz[len]=siz[i];
	pre[1]=1;
	for(int i=1;i<=len;i++)
		pre[i+1]=pre[i]*(siz[i]+1);
	for(int i=1;i<pre[len+1];i++)
	{
		decode(i,cur);
		f[i]=1e18;
		int num=0;
		for(int j=1;j<=len;j++) num+=cir[j]*cur[j];
		for(int j=1;j<=len;j++)
		{
			if(!cur[j]) continue;
			ll w=max(f[i-pre[j]],g[num-cir[j]+1][num]);
			if(w<f[i]) f[i]=w,from[i]=j;
		}
	}
	printf("%lld\n",f[pre[len+1]-1]);
	int now=pre[len+1]-1,k=n;
	while(now)
	{
		int L=cir[from[now]],cur=E[L].back();E[L].pop_back();
		solve(w+k-L,L);
		for(int i=1;i<=L;i++)
		{
			res[cur]=b[i];
			cur=p[cur];
		}
		now-=pre[from[now]];k-=L;
	}
	for(int i=1;i<=n;i++) printf("%d ",res[i]);
	return 0;
}
posted @ 2024-11-01 10:06  g1ove  阅读(11)  评论(0编辑  收藏  举报