Codeforces #688 A - E 题解
前言
第一次打进 rank100 的 div2,泪目
A - Cancel the Trains
题目大意
在一个 \(100 \times 100\) 的矩形中有若干列和若干行有一辆位于 左/下 且速度为 \(1\) 的车向 右/上 方向行驶,问最少去掉多少辆车才能不使任何两辆车相撞。
解题思路
简单观察就能发现两辆车会相撞当且仅当他们所在的行数与列数相等。
直接统计有多少个行列相同的车即可。
Code
const int N=105;
int t,n,m,vis[N];
signed main()
{
t=read();
while(t--)
{
n=read();m=read();
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i)
vis[read()]++;
for(int i=1;i<=m;++i)
vis[read()]++;
int res=0;
for(int i=1;i<=100;++i)
if(vis[i])
res+=vis[i]-1;
printf("%lld\n",res);
}
return 0;
}
B - Suffix Operations
题目大意
给一个序列,开始可以任意修改一个数,问修改后的序列最少通过几次后缀加和后缀减使得整个序列的元素相等。
解题思路
先考虑如果没有修改一个数的操作答案应该怎么算。
对于三个数 \(x,y,z\):
如果要使得 \(y=x\) ,那么首先要把 \(y,z\) 这个后缀加上 \((y-x)\) ,
这样这三个数就变成了 \(x,y-(y-x),z-(y-x)\) 即 \(x,x,z-y+x\)。
再着如果要使得 \(z-y+x=x\),就要把 \(z\) 这个后缀修加上 \((z-y)\) ,
这样这三个数就变成了 \(x,x,z-y+x-(z-y)\) 即 \(x,x,x\)。
因此,让 \(x,y,z\) 相等的修改次数就是 \(|y-x|+|z-y|\)。
显然这个结论可以推广到 \(n\) 个数的情况,即 \(n\) 个数相等的修改次数是 \(\sum\limits_{i=2}^n|a_i-a_{i-1}|\) 。
而如果有一次修改机会的话直接枚举修改位置 \(i\),那么显然把 \(a_i\) 修改为 \(a_{i-1}\) 与 \(a_{i+1}\) 之间的一个任意数是最优的。
而此时 \(a_{i-1},a_i,a_{i+1}\) 的代价就由 \(|a_i-a_{i-1}|+|a_{i+1}-a_i|\) 变成了 \(|a_{i+1}-a_{i-1}|\),因此找出修改一次可以使答案减少的最大值就行了。
注意 \(i=1 \text{ or } n\) 的情况是需要特判的,代价由 \(a_1 \text{ or } a_n\) 变为 \(0\)。
Code
const int N=2e5+5;
int t,n,a[N];
signed main()
{
t=read();
while(t--)
{
n=read();
for(int i=1;i<=n;++i)
a[i]=read();
int mmax=0,pos=-1;
for(int i=2;i<=n-1;++i)
if(abs(a[i]-a[i-1])+abs(a[i]-a[i+1])-abs(a[i+1]-a[i-1])>mmax)
{
mmax=abs(a[i]-a[i-1])+abs(a[i]-a[i+1])-abs(a[i+1]-a[i-1]);
pos=i;
}
if(abs(a[1]-a[2])>mmax) mmax=abs(a[1]-a[2]),pos=1;
if(abs(a[n]-a[n-1])>mmax) mmax=abs(a[n]-a[n-1]),pos=n;
if(pos==1) a[1]=a[2];
else if(pos==n) a[n]=a[n-1];
else a[pos]=(a[pos-1]+a[pos+1])/2;
int res=0;
for(int i=2;i<=n;++i)
res+=abs(a[i]-a[i-1]);
printf("%lld\n",res);
}
return 0;
}
C - Triangles
题目大意
给一个数字正方形,对于每个数字,选三个等于这个数字的点,且这三个点两两连成的选段中至少有一条与正方形的一边平行,求最大三角形面积。
数字范围 \(0 \sim 9\) 且每次可以任意修改一个位置的数字,修改无后效性。
解题思路
显然修改只有一下几种可能:
-
修改平行边中的一个点,即增长三角形的底。
-
修改平行边外的一个点,即增长三角形的高。
可以直接预处理每行每列每个权值第一次出现的位置与最后一次出现的位置,然后枚举每种数字,再枚举行,求出这一行为底的面积最大为多少,这步分类讨论贪心即可,再枚举列,处理方法同理。
Code
const int N=2005;
int t,n,firh[N][10],enh[N][10],firl[N][10],enl[N][10];
char s[N][N];
signed main()
{
t=read();
while(t--)
{
n=read();
for(int i=1;i<=n;++i)
for(int j=0;j<=9;++j)
{
firh[i][j]=-1;enh[i][j]=-1;
firl[i][j]=-1;enl[i][j]=-1;
}
for(int i=1;i<=n;++i)
scanf("%s",s[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
if(firh[i][s[i][j]-'0']==-1)
firh[i][s[i][j]-'0']=j;
enh[i][s[i][j]-'0']=j;
}
for(int j=1;j<=n;++j)
for(int i=1;i<=n;++i)
{
if(firl[j][s[i][j]-'0']==-1)
firl[j][s[i][j]-'0']=i;
enl[j][s[i][j]-'0']=i;
}
for(int p=0;p<=9;++p)
{
int res=0;
int mmax=-1,mmin=1e18;
for(int i=1;i<=n;++i)
{
if(enl[i][p]!=-1) mmax=max(mmax,enl[i][p]);
if(firl[i][p]!=-1) mmin=min(mmin,firl[i][p]);
}
for(int i=1;i<=n;++i)
{
if(mmax!=-1&&firh[i][p]!=-1) res=max(res,abs(firh[i][p]-n)*abs(i-mmax));
if(mmin!=1e18&&firh[i][p]!=-1) res=max(res,abs(firh[i][p]-n)*abs(i-mmin));
if(mmax!=-1&&enh[i][p]!=-1) res=max(res,(enh[i][p]-1)*abs(i-mmax));
if(mmin!=1e18&&enh[i][p]!=-1) res=max(res,(enh[i][p]-1)*abs(i-mmin));
if(firh[i][p]!=-1&&enh[i][p]!=-1) res=max(res,abs(firh[i][p]-enh[i][p])*abs(i-n));
if(firh[i][p]!=-1&&enh[i][p]!=-1) res=max(res,abs(firh[i][p]-enh[i][p])*abs(i-1));
}
mmax=0,mmin=1e18;
for(int i=1;i<=n;++i)
{
if(enh[i][p]!=-1) mmax=max(mmax,enh[i][p]);
if(firh[i][p]!=-1) mmin=min(mmin,firh[i][p]);
}
for(int i=1;i<=n;++i)
{
if(mmax!=-1&&firl[i][p]!=-1) res=max(res,abs(firl[i][p]-n)*abs(i-mmax));
if(mmin!=1e18&&firl[i][p]!=-1) res=max(res,abs(firl[i][p]-n)*abs(i-mmin));
if(mmax!=-1&&enl[i][p]!=-1) res=max(res,(enl[i][p]-1)*abs(i-mmax));
if(mmin!=1e18&&enl[i][p]!=-1) res=max(res,(enl[i][p]-1)*abs(i-mmin));
if(firl[i][p]!=-1&&enl[i][p]!=-1) res=max(res,abs(firl[i][p]-enl[i][p])*abs(i-n));
if(firl[i][p]!=-1&&enl[i][p]!=-1) res=max(res,abs(firl[i][p]-enl[i][p])*abs(i-1));
}
printf("%lld ",res);
}
printf("\n");
}
return 0;
}
D - Checkpoints
题目大意
若干个关卡,其中某些关卡使检查点,玩家一次通过关卡,每走到一个检查点就能激活这个检查点。
对于每一关,玩家挑战一次通过的概率为 \(\dfrac{1}{2}\),若玩家没有通过则回到最近激活的一个检查点。
构造一个的关卡序列使得玩家通过所有关卡的期望次数为 \(k\ (k \le 10^{18})\)
解题思路
对于任意一个关卡,如果到达这个关卡并通过它的概率为 \(p\),那么显然通过它的次数的期望就是 \(\dfrac{1}{p}\)。
以下用 \(\text{C}\) 表示检查点关卡,\(\text{N}\) 表示普通关卡。
对于一个单独的 \(\text{C}\) ,因为通过它的概率是 \(\dfrac{1}{2}\),那么通过它的期望次数为 \(2\)。
而如果一个 \(\text{C}\) 后跟有若干个连续的 \(\text{N}\) ,那么对于第 \(i\) 个 \(\text{N}\) ,到达并通过它的概率是 \(\dfrac{1}{2^{i+1}}\) ,因此通过它的期望次数就是 \(2^{i+1}\)。
因此对于一个 \(\text{C}\) 后加上 \(p\) 个 \(\text{N}\) 的组合,它对答案期望的贡献就是 \(2^1 + 2^2 + ... + 2^{p+1}=2^{p+2}-2\)
所以只要再在结尾上添加上一个 \(\text{C}\),这个 \(\text{CN...NC}\) ( \(p\) 个 \(\text{N}\) )的组合对答案期望的贡献就是 \(2^{p+2}\)。
综上我们可以通过组合 \(\text{C}\), \(\text{N}\) 来凑出 \(2^1,2^2,2^3,...\)
因此可以发现若 \(k\) 为奇数一定是无解的。
否则直接把 \(k\) 二进制拆分即可。
Code
const int N=2005;
int t,k,a[N];
signed main()
{
t=read();
while(t--)
{
k=read();
if(k%2==1)
{
printf("-1\n");
continue;
}
int sum=0;
for(int i=60;i>=1;--i)
if((k>>i)&1)
{
a[++sum]=1;
for(int j=1;j<=i-2;++j)
a[++sum]=0;
if(i!=1) a[++sum]=1;
}
printf("%lld\n",sum);
for(int i=1;i<=sum;++i)
printf("%lld ",a[i]);
printf("\n");
}
return 0;
}
E - Dog Snacks
题目大意
给一棵边权为 \(1\) 的树,每个节点都有一个物品,一个人从 \(1\) 节点开始,每次可以选择走到与这个人所在节点距离小于等于 \(k\) 中最近的且有物品的节点中的任意一个并取走这个节点的物品。
如果这个人能取走所有物品并且最终回到 \(1\) 节点(即最后一个取完物品的节点与 \(1\) 节点的距离小于等于 \(k\))那么他就能胜利。
求最小的能保证胜利的 \(k\)。
解题思路
\(k\) 显然满足单调性,因此可以二分,这样问题就转化为了判断一个 \(k\) 是否能满足条件。
一个显然的结论是对于一棵子树,只有遍历完才会跳出这棵子树。
假设 \(f(s)\) 表示以 \(s\) 为根的子树遍历完后要跳出这棵子树需要走的最短步数,那么:
直接看所有 \(f(s)\) 是否都小于等于 \(k\) 即可。
对于 \(s=1\) 的情况要特判下。
Code
const int N=2e5+5;
int t,n,k,flag;
struct Edge
{
int v,ne;
}e[N*2];
int head[N],tot;
inline void add(int u,int v);
int dfs(int u,int fa);
signed main()
{
t=read();
while(t--)
{
tot=0;
memset(head,0,sizeof(head));
n=read();
for(int i=1;i<=n-1;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
int l=0,r=n,res=-1;
while(l<=r)
{
k=mid;flag=1;
dfs(1,0);
if(flag)
{
res=k;
r=mid-1;
}
else l=mid+1;
}
printf("%lld\n",res);
}
return 0;
}
inline void add(int u,int v)
{
e[++tot]=(Edge){v,head[u]};
head[u]=tot;
}
int dfs(int u,int fa)
{
vector<int> p;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa) continue;
p.push_back(dfs(v,u)+1);
}
sort(p.begin(),p.end());
int used=0;
if(u==1) used=1;
for(int i=0;i<p.size();++i)
if(p[i]>k)
{
if(used&&p[i]-1<=k)
used=0;
else flag=0;
}
if(p.size()==0) return 1;
else return p[0];
}
Upd
模拟赛又遇到了这题,并写了线性做法。。。
Code
const int N=2e5+5;
int T,n,ans,mn[N];
struct Edge
{
int v,ne;
}e[N*2];
int head[N],tot;
inline void add(int u,int v);
void dfs(int u,int fa);
signed main()
{
T=read();
while(T--)
{
n=read();tot=0;ans=1;
for(int i=1;i<=n;++i)
mn[i]=1e9,head[i]=0;
for(int i=1;i<=n-1;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs(1,0);
write(ans);puts("");
}
return 0;
}
inline void add(int u,int v)
{
e[++tot]=(Edge){v,head[u]};
head[u]=tot;
}
void dfs(int u,int fa)
{
int flag=0;
int mx1=-1,mx2=-1;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);flag=1;
mn[u]=min(mn[u],mn[v]+1);
if(mn[v]+1>mx1)
mx2=mx1,mx1=mn[v]+1;
else if(mn[v]+1>mx2)
mx2=mn[v]+1;
}
if(!flag) mn[u]=0;
if(u==1) ans=max(ans,mx2+1);
else ans=max(ans,mx1+1);
}