UOJ501 【JOISC2020】建筑装饰 4
UOJ501 【JOISC2020】建筑装饰 4
动态规划
先给出一种巨佬指导的乱搞,对序列进行二分,序列左边尽量使用\(A\),右侧尽量使用\(B\),然后凑出\(n\),如果没有答案,序列左边尽量使用\(B\),右侧尽量使用\(A\),再添加一些调整就直接通过了本题。
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1000005
using namespace std;
const int INF=1000000007;
int n,a[N],b[N],c[N];
int ans[N],dp[N][2],pre[N][2];
bool flag;
bool ok(int x)
{
return x!=-1 && x!=INF;
}
void ckmax(int &x,int y)
{
x=(x>y)?x:y;
}
void ckmin(int &x,int y)
{
x=(x<y)?x:y;
}
bool fcheck()
{
int mn=min(a[1],b[1]);
for (int i=2;i<=n;++i)
{
if (mn<=min(a[i],b[i]))
mn=min(a[i],b[i]); else
if (mn>max(a[i],b[i]))
return false; else
mn=max(a[i],b[i]);
}
return true;
}
int check(int mid)
{
dp[1][0]=1,dp[1][1]=0;
for (int i=2;i<=n;++i)
{
if (i<=mid)
{
dp[i][0]=dp[i][1]=-1;
if (a[i-1]<=a[i] && ok(dp[i-1][0]))
ckmax(dp[i][0],dp[i-1][0]+1);
if (b[i-1]<=a[i] && ok(dp[i-1][1]))
ckmax(dp[i][0],dp[i-1][1]+1);
if (a[i-1]<=b[i] && ok(dp[i-1][0]))
ckmax(dp[i][1],dp[i-1][0]);
if (b[i-1]<=b[i] && ok(dp[i-1][1]))
ckmax(dp[i][1],dp[i-1][1]);
} else
{
dp[i][0]=dp[i][1]=INF;
if (a[i-1]<=a[i] && ok(dp[i-1][0]))
ckmin(dp[i][0],dp[i-1][0]+1);
if (b[i-1]<=a[i] && ok(dp[i-1][1]))
ckmin(dp[i][0],dp[i-1][1]+1);
if (a[i-1]<=b[i] && ok(dp[i-1][0]))
ckmin(dp[i][1],dp[i-1][0]);
if (b[i-1]<=b[i] && ok(dp[i-1][1]))
ckmin(dp[i][1],dp[i-1][1]);
}
}
if (mid>n)
return max(dp[n][0],dp[n][1]);
return min(dp[n][0],dp[n][1]);
}
void cmax(int &x,int &_x,int y,int _y)
{
if (x<y)
x=y,_x=_y;
}
void cmin(int &x,int &_x,int y,int _y)
{
if (x>y)
x=y,_x=_y;
}
void calc(int mid)
{
dp[1][0]=1,dp[1][1]=0;
for (int i=2;i<=n;++i)
{
if (i<=mid)
{
dp[i][0]=dp[i][1]=-1;
if (a[i-1]<=a[i] && ok(dp[i-1][0]))
cmax(dp[i][0],pre[i][0],dp[i-1][0]+1,0);
if (b[i-1]<=a[i] && ok(dp[i-1][1]))
cmax(dp[i][0],pre[i][0],dp[i-1][1]+1,1);
if (a[i-1]<=b[i] && ok(dp[i-1][0]))
cmax(dp[i][1],pre[i][1],dp[i-1][0],0);
if (b[i-1]<=b[i] && ok(dp[i-1][1]))
cmax(dp[i][1],pre[i][1],dp[i-1][1],1);
} else
{
dp[i][0]=dp[i][1]=INF;
if (a[i-1]<=a[i] && ok(dp[i-1][0]))
cmin(dp[i][0],pre[i][0],dp[i-1][0]+1,0);
if (b[i-1]<=a[i] && ok(dp[i-1][1]))
cmin(dp[i][0],pre[i][0],dp[i-1][1]+1,1);
if (a[i-1]<=b[i] && ok(dp[i-1][0]))
cmin(dp[i][1],pre[i][1],dp[i-1][0],0);
if (b[i-1]<=b[i] && ok(dp[i-1][1]))
cmin(dp[i][1],pre[i][1],dp[i-1][1],1);
}
}
int k;
if (mid>n)
k=max(dp[n][0],dp[n][1]); else
k=min(dp[n][0],dp[n][1]);
if (!ok(k))
return;
if (k==dp[n][0])
{
int x=n,_x=0;
ans[x]=0;
while (x!=1)
{
_x=pre[x][_x],--x;
ans[x]=_x;
}
} else
{
int x=n,_x=1;
ans[x]=1;
while (x!=1)
{
_x=pre[x][_x],--x;
ans[x]=_x;
}
}
int del=k-(n >> 1);
if (!del)
{
flag=true;
return;
}
for (int i=1;i<=n;++i)
{
int nxt((i==n)?INF:(ans[i+1]?b[i+1]:a[i+1]));
int pre((i==1)?-INF:(ans[i-1]?b[i-1]:a[i-1]));
if (del>0 && !ans[i] && pre<=b[i] && b[i]<=nxt)
ans[i]=1,--del;
if (del<0 && ans[i] && pre<=a[i] && a[i]<=nxt)
ans[i]=0,++del;
}
if (!del)
flag=true;
}
int main()
{
scanf("%d",&n),n <<=1;
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int i=1;i<=n;++i)
scanf("%d",&b[i]);
if (!fcheck())
{
puts("-1");
return 0;
}
int l(1),r(n+1);
while (l<=r)
{
int mid(l+r >> 1);
int c(check(mid));
if (c==(n >> 1))
{
calc(mid);
for (int i=1;i<=n;++i)
putchar(ans[i]?'B':'A');
putchar('\n');
return 0;
}
if (c<(n >> 1))
l=mid+1; else
r=mid-1;
}
for (int i=1;i<=n;++i)
swap(a[i],b[i]);
l=1,r=n+1;
while (l<=r)
{
int mid(l+r >> 1);
int c(check(mid));
if (c==(n >> 1))
{
calc(mid);
for (int i=1;i<=n;++i)
putchar(ans[i]?'A':'B');
putchar('\n');
return 0;
}
if (c<(n >> 1))
l=mid+1; else
r=mid-1;
}
for (int i=1;i<=n;++i)
swap(a[i],b[i]);
for (int i=1;i<=n+1;++i)
{
calc(i);
if (flag)
{
for (int j=1;j<=n;++j)
putchar(ans[j]?'B':'A');
putchar('\n');
return 0;
}
}
puts("-1");
return 0;
}
正解:设\(dp_{i,0/1}\)表示第\(i\)个位置,选择\(A\)或\(B\)的状态,满足条件的\(A\)的个数一定是一段区间,我们直接用一个\(pair\)类型表示可能选择\(A\)的个数的范围作为\(dp\)值进行转移即可。