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