AtCoder Grand Contest 048
链接
C. Penguin Skating
题解
容易发现企鹅之间的相对顺序永远不会变,令 \(a_i'=a_i-i,b_i'=b_i-i\),那么可以看做每次让 \(a_i=a_{i+1}\) 或者 \(a_i=a_{i-1}\)。
这样最后一定是某一段区间取一个值,然后为了达到这个值需要让某些点先运动一次。具体来说假设当前连续相同 \(b\) 区间是 \([x,y]\),\(a_i\) 值与其相同的区间是 \([l,r]\),那么 \([x,l)\) 区间的企鹅需要运动一次,而 \((r,y]\) 区间的企鹅需要先垫一下。
直接双指针,复杂度 \(O(n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int a[N],b[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]-=i;
for(int i=1;i<=n;i++) scanf("%d",&b[i]),b[i]-=i;
m-=n,a[n+1]=m,b[0]=0;
long long ans=0;
for(int i=1,p=1,l=0,r=0;i<=n;i=p+1)
{
for(p=i;p<n && b[p+1]==b[p];p++);
while(l<=n+1 && a[l]<b[i]) l++;
while(r<=n && a[r+1]<=b[i]) r++;
if(l>r){puts("-1");return 0;}
ans+=max(l-i,0)+max(p-r,0);
}
printf("%lld\n",ans);
return 0;
}
D. Pocky Game
题解
很妙的题。
首先容易证明,当左方先手时,最左边一堆的石子一定是越多越有利,右方同理。
考虑 dp,设 \(f_{l,r}/g_{l,r}\) 表示左先/右先情况下,要赢得 \([l,r]\) 区间的胜利,最左/右端石子至少要多少个。
容易发现,每个人的决策实际上只有取 \(1\) 个石子和取完一堆石子。不妨考虑 \(f_{l,r}\) 的转移:
- 如果 \(g_{l+1,r}>a_r\),即取完 \(a_l\) 后右方必败,那么只要 \(a_l\) 有石子即可获胜。即 \(f_{l,r}=1\)。
- 否则只能一个一个取。考虑此时右方的决策,因为左方想尽可能减少 \(f_{l,r}\),那么右方就应该尽可能增大 \(f_{l,r}\),所以他会一直取一个石子直到 \(g_{l+1,r}\) 为止。因为如果此时继续取,那么左方直接取完整堆石子就获胜了。所以之后右方会取走 \(a_r\) 的所有石子,变成到 \(f_{l,r-1}\)。
所以 \(f_{l,r}=a_r-g_{l+1,r}+f_{l,r-1}+1\)。
\(g_{l,r}\) 同理。复杂度 \(O(n^2)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 110
using namespace std;
typedef long long ll;
int a[N];ll l[N][N],r[N][N];
int main()
{
int t;
scanf("%d",&t);
while(t --> 0)
{
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=n-1;i;i--)
{
l[i][i+1]=a[i+1]+1,r[i][i+1]=a[i]+1;
for(int j=i+2;j<=n;j++)
{
if(r[i+1][j]>a[j]) l[i][j]=1;
else l[i][j]=(a[j]-r[i+1][j])+l[i][j-1]+1;
if(l[i][j-1]>a[i]) r[i][j]=1;
else r[i][j]=(a[i]-l[i][j-1])+r[i+1][j]+1;
}
}
puts(l[1][n]<=a[1]?"First":"Second");
}
return 0;
}
E. Strange Relation
题解
神仙题。
考虑一个 \(x_i\) 的必要条件:如果存在 \(i\) 满足 \(A_i+Tx_i\in(A_1-T,A_1]\),那么找到满足该条件最靠左的点,将它的 \(x_i\) 增加 \(1\),然后调整它后面的权值。容易证明这样总是合法的。
如果不存在这样的值,那么考虑将 \(A_1\) 删去,将所有 \(>A_1\) 的 \(x_i\) 减 \(1\),容易发现这样得到的 \(x'\) 是符合要求的。可以证明,\(\{x_i\}\) 字典序最大当且仅当 \(\{x_i'\}\) 的字典序最大,而 \(\{x_i'\}\) 的处理方式可以递归得到。最后如果 \(A\) 只剩一个元素,显然 \(x_1\) 为 \(0\)。这样反向构造上来,得到的 \(\{x_i\}\) 一定是字典序最小的。
考虑 dp。对每个位置的每个值单独考虑 \(x_i=j\) 的方案数。具体来说考虑从后往前 dp,容易发现无论怎么取值最后总是存在合法的构造,所以只需要记录当前 \(x_i\) 的值。如果往前枚举的值 \(l\) 满足 \(A_l-T<A_i+Tx_i\),那么让 \(x_i\) 加一。
复杂度 \(O(n^3k^2)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=60,mod=1000000007;
int a[N][N],f[N][N];
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int main()
{
int n,k,T;scanf("%d%d%d",&n,&k,&T);
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++) scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
{
int s=1,ans=0;
for(int j=i+1;j<=n;j++) s=1ll*s*k%mod;
for(int j=1;j<=k;j++)
{
memset(f[i],0,sizeof(f[i]));
f[i][0]=s;
for(int l=i-1;l;l--)
{
memset(f[l],0,sizeof(f[l]));
for(int y=0;y<i;y++) if(f[l+1][y])
for(int w=1;w<=k;w++)
{
if(a[l][w]-T<a[i][j]+y*T) add(f[l][y+1],f[l+1][y]);
else add(f[l][y],f[l+1][y]);
}
}
for(int w=0;w<i;w++) add(ans,1ll*f[1][w]*w%mod);
}
printf("%d\n",ans);
}
return 0;
}
F. 01 Record
不会。

浙公网安备 33010602011771号