Atcoder Grand Contest 011&012
012E Camel and Oases
题目描述
解法
考试时直接切了,不知道这题有什么难的,我都会做的题肯定是水题。
首先有一个问题转化:我们可以将原序列划分为 \(\log\) 个连续段,使得每一段的 \(\max(x_i-x_{i-1})\leq v_j\),其中 \(v_j=\frac{k}{2^j}\) 表示第 \(j\) 次跳跃后背包的容量(注意 \(v_j=0\) 也是合法的,但是只能存在一次)
首先考虑全局存不存在合法方案,一个重要的观察是只要我们确定 \(v\) 的分配顺序,那么可以通过贪心来确定段的划分,就是按照这个顺序能取就取。解决顺序问题可以考虑状压 \(dp\),设 \(dp[s]\) 表示已使用的 \(v\) 集合为 \(s\) 的最远延伸距离。
那么怎么确定单个位置的答案呢?一个简单的观察是:\(x_i-x_{i-1}>k\) 的最多只存在 \(\log k\) 对,要不然就全局无解。所以我们以 \(x_i-x_{i-1}>k\) 为断点,由于每个点为起点都会取遍段的位置之后再离开,段中每个位置的答案是一样的。
设段是 \([l,r]\),设 \(f(s),g(s)\) 分别表示前缀 \(/\) 后缀的最远延伸位置,那么可以枚举集合 \(s\),设补集是 \(t\),判断条件就是:
都可以暴力做,时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1<<21;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,t,v[30],p[30],l[22][M],r[22][M],x[M],f[M],g[M];
signed main()
{
n=read();m=read();
for(int x=m;x;x/=2) v[k++]=x;v[k++]=0;
for(int i=1;i<=n;i++) x[i]=read();
for(int w=0;w<k;w++)
{
l[w][0]=l[w][1]=1;
r[w][n]=r[w][n+1]=n;
for(int i=2;i<=n;i++)
{
if(x[i]-x[i-1]<=v[w])
l[w][i]=l[w][i-1];
else l[w][i]=i;
}
for(int i=n-1;i>=1;i--)
{
if(x[i+1]-x[i]<=v[w])
r[w][i]=r[w][i+1];
else r[w][i]=i;
}
}
memset(g,0x3f,sizeof g);g[0]=n+1;
for(int s=0;s<(1<<k);s++)
for(int i=0;i<k;i++) if(!(s>>i&1))
{
f[s|(1<<i)]=max(f[s|(1<<i)],r[i][f[s]+1]);
g[s|(1<<i)]=min(g[s|(1<<i)],l[i][g[s]-1]);
}
for(int i=2;i<=n;i++)
if(x[i]-x[i-1]>m) p[++t]=i;
if(t>k)
{
for(int i=1;i<=n;i++) puts("Impossible");
return 0;
}
for(int x=1,y;x<=n;x=y+1)
{
y=r[0][x];int fl=0,t=(1<<k)-1-1;
for(int s=0;s<(1<<k);s++) if(!(s&1))
fl|=(f[s]>=x-1 && g[s^t]<=y+1);
for(int i=x;i<=y;i++)
puts(fl?"Possible":"Impossible");
}
}
011D Half Reflector
题目描述
解法
首先要知道单次操作之后会有什么样的变化,如果首位是 A
那么会立马弹开,如果首位是 B
,那么经过它之后会变成 A
,我们继续考虑后续的情况,关键的观察是球的上一个位置一定是 A
:
- 下一个位置是
B
,那么对于AB
会变成AA
- 下一个位置是
A
,那么对于AA
会变成BA
使用归纳法,我们可以发现球的作用相当于给每个位置左移之后取反(在环的意义下,正好最后一个位置一定是 A
)
那么就可以通过打整体标记实现 \(O(k)\) 模拟了。考试时我观察数据发现后缀会趋向 ABABAB...
这种类型,而且在 \(2n\) 次之内就会稳定下来,但是我们需要保留原来 \(k\) 的奇偶性,这样我们就可以在 \(O(n)\) 的时间内模拟了。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,a[M];char s[M];
signed main()
{
n=read();k=read();scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'A';
int t=0,p=1;k=min(k,2*n+(k&1));
while(k--)
{
if(t^a[p]) t^=1,p=p%n+1;
else a[p]^=1;
}
for(int i=p;i<=n;i++) putchar((a[i]^t)?'B':'A');
for(int i=1;i<p;i++) putchar((a[i]^t)?'B':'A');
}
012F Prefix Median
题目描述
解法
直接按照生成方式去规划 \(b\) 序列是困难的,我们需要先寻找一些 \(b\) 序列的性质。我们可以感受到,\(b\) 序列的每个元素都有其大致的范围,并且 \(b\) 序列的变化是连续的,那么写出必要条件:
- \(b_i\in\{a_1,a_2...a_{2n-1}\}\),显然的必要性。
- \(a_i\leq b_i\leq a_{2n-i}\),容易发现不在这个范围内一定无法成为中位数。
- 对于 \(i\) 和 \(i+1\),不存在 \(j\leq i\) 使得 \(p_i<p_j<p_{i+1}\) 或者 \(p_i>p_j>p_{i+1}\),因为中位数只能移动一个位置,这里要加 \(j\leq i\) 的原因就是只考虑前面出现过的数(所以这部分 soulist 讲错了)
那么用上面的必要条件来构造即可证明充分性。考虑根据 \(p_{i+1}\) 和 \(p_i\) 的大小关系来构造,如果 \(p_i=p_{i+1}\) 那么添加剩下的最大值和最小值;如果 \(p_i>p_{i+1}\),并且如果 \(p_{i+1}\) 是第一次出现,那么添加 \(p_{i+1}\) 和最小值,否则添加最小值和次小值;如果 \(p_i<p_{i+1}\),并且如果 \(p_{i+1}\) 是第一次出现,那么添加 \(p_{i+1}\) 和最大值,否则添加最大值和次大值。这样构造一定有解。
那么用这个等价条件去规划 \(b\) 序列即可,首先我们把 \(a_i\) 排序,观察到 \(b_n=a_n\),那么我们以 \(n\) 为起始点往回推。由于 \(b\) 取值范围的变化我们需要加入 \(a_{i}\) 和 \(a_{2n-i}\),我们已知 \(p_{i+1}\) 现在要确定 \(p_i\),确定之后我们需要删除 \((p_i,p_{i+1})\) 中的所有数。
设 \(x=p_i\),那么我们记录 \(x\) 左边有多少数,\(x\) 右边有多少数即可,所以设 \(dp[i][l][r]\) 表示考虑到 \(p_i\),\(x\) 左边有 \(l\) 个数,\(x\) 右边有 \(r\) 个数的方案数。转移枚举 \(x'\) 即可,注意相同的数加入的时候需要当成一个数。
时间复杂度 \(O(n^4)\)
#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 105;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,a[M],dp[M][M][M];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
n=read();m=2*n;
for(int i=1;i<m;i++) a[i]=read();
sort(a+1,a+m);
dp[n][0][0]=1;
for(int i=n-1;i>=1;i--)
{
int x=a[i]!=a[i+1],y=a[m-i]!=a[m-i-1];
for(int l=0;l<m;l++) for(int r=0;r<m;r++)
{
int t=dp[i+1][l][r];if(!t) continue;
add(dp[i][l+x][r+y],t);
for(int d=0;d<l+x;d++)
add(dp[i][d][r+y+1],t);
for(int d=0;d<r+y;d++)
add(dp[i][l+x+1][d],t);
}
}
for(int l=0;l<m;l++) for(int r=0;r<m;r++)
add(ans,dp[1][l][r]);
printf("%d\n",ans);
}