AtCoder Grand Contest 049
Preface
新出的AGC好难啊,只会A~D,EF完全不会(E想了半小时看题解半小时还是不会)
A - Erasing Vertices
首先容易发现当一个点被删去后保留它对答案并无任何影响
利用期望的线性性,我们发现对于若能到达某个点\(i\)的点有\(c_i\)个,则\(i\)对答案的贡献就是\(\frac{1}{c_i}\)
复杂度\(O(n^3)\)(可以用bitset
优化但没必要)
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int n,c[N],p[N][N]; char s[N][N]; double ans;
int main()
{
RI i,j,k; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%s",s[i]+1);
for (i=1;i<=n;++i) for (j=1;j<=n;++j) p[i][j]=s[i][j]-'0';
for (i=1;i<=n;++i) p[i][i]=1;
for (k=1;k<=n;++k) for (i=1;i<=n;++i) for (j=1;j<=n;++j)
if (p[i][k]&&p[k][j]) p[i][j]=1;
for (i=1;i<=n;++i) for (j=1;j<=n;++j) c[i]+=p[j][i];
for (i=1;i<=n;++i) ans+=1.0/c[i]; return printf("%.10lf",ans),0;
}
B - Flip Digits
考虑变换的本质,其实就是把\(01\to 10\)或\(11\to 00\)
转化一下我们发现其实就是把一个\(1\)往前移动一位或者是将两个相邻的\(1\)消除
统计出\(S\)中每个\(1\)的位置,贪心地移动一下即可,具体实现看代码
复杂度\(O(n)\)
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int n,pos[N],cnt,cur,c1,c2; char s[N],t[N]; long long ans;
int main()
{
RI i; for (scanf("%d%s%s",&n,s+1,t+1),i=1;i<=n;++i)
if (c1+=s[i]-'0',c2+=t[i]-'0',s[i]=='1') pos[++cnt]=i;
for (i=1;i<=n;++i) if (t[i]=='1')
{
if (cur+1<=cnt) ans+=pos[++cur]-i; else return puts("-1"),0;
} else
{
if (cur<cnt&&pos[cur+1]<=i)
{
if ((c1-=2)<c2) return puts("-1"),0;
ans+=pos[cur+2]-pos[cur+1]; cur+=2;
}
}
return printf("%lld",ans),0;
}
C - Robots
STO CXR ORZ
很显然我们可以把所有机器人分为两类:无影响和有影响的
具体的,无影响的就是\(a_i>b_i\)的机器人,反之有影响的就是的\(a_i\le b_i\)的机器人
对所有有影响的机器人\(i\)我们可以定义两种操作:
- 收回:花费\(b_i-(a_i-1)\)的代价将\(i\)变为\(a_i-1\),然后让\(i\)把\([1,i)\)的机器人都无脑踩死,容易发现此时腾出了\(b_i-(a_i-1)\)次修改\(b_j\)的机会
- 践踏:指通过其它无影响的机器人将\(i\)踩死,若不存在则我们可以利用“收回”操作中腾出的修改在\(a_i+1\)处放一个机器人然后让它踩死\(i\),不难证明这样是最优的
接下来我们神奇地发现最优情况下,“收回”操作至多只执行一次,因为若执行两次及以上的话只需要保留最后一个即可
因此我们直接枚举每一个有影响的机器人,假设在它的位置执行“收回”即可,显然所有在\(i\)后面的有影响且无法被预先踩死的机器人都要执行“践踏”操作
列一下判断踩死的式子我们发现只要倒着维护\(a_i-b_i\)的最小值即可
复杂度\(O(n)\)
#include<cstdio>
#include<set>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e9;
int n,a[N],b[N],ans=INF,cur,mi=INF; set <int> s;
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) scanf("%d",&b[i]);
for (b[0]=-1,i=n;~i;--i) if (!i||a[i]<=b[i])
ans=min(ans,max(b[i]-(a[i]-1),cur)),cur+=(a[i]<mi);
else mi=min(mi,a[i]-b[i]); return printf("%d",ans),0;
}
D - Convex Sequence
很套路的一道题吧,稍微转化一下就会发现第二个限制就是原序列的二阶差分恒大于等于\(0\)
我们设序列的最小值在\(i\),考虑这样构造最后的序列:
-
将整个序列都加上\(1\)
-
选择一个\(j<i\),将\(a_1,a_2,\cdots,a_j\)分别加上\(j,j-1,\cdots,1\),此时的贡献为\(\frac{j(j+1)}{2}\),为了避免重复我们需要对\(i-1\)进行至少一次这样的操作
-
选择一个\(j>i\),将\(a_j,a_{j+1},\cdots,a_n\)分别加上\(1,2,\cdots,j\),此时的贡献为\(\frac{j(j+1)}{2}\)
容易发现由于后面的贡献小于等于\(m\)的是\(\sqrt m\)级别的,因此如果我们暴力做背包的话复杂度是\(O(m\sqrt m)\)的
现在的问题就是当\(i\)移动时答案的变化了,容易发现从\(i-1\)移动到\(i\)时相当于删除了原来的一个元素并添加了一个新的元素,显然无穷背包是可以\(O(m)\)回退的
由于我们最多只需要移动\(\sqrt m\)次,因此这部分的复杂度也是\(O(m\sqrt m)\)的
总复杂度\(O(m\sqrt m)\)
#include<cstdio>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=100005,mod=1e9+7;
int n,m,f[N],ans;
inline int S(CI x) { return x*(x+1)/2LL; }
inline void expand(CI x)
{
for (RI i=x;i<=m;++i) (f[i]+=f[i-x])%=mod;
}
inline void revoke(CI x)
{
for (RI i=m;i>=x;--i) (f[i]+=mod-f[i-x])%=mod;
}
signed main()
{
RI i; scanf("%lld%lld",&n,&m); f[0]=1; expand(n);
for (i=1;i<=n-1;++i) expand(S(i));
for (i=1;i<=n;++i) (ans+=(m>=S(i-1)?f[m-S(i-1)]:0))%=mod,revoke(S(n-i)),expand(S(i));
return printf("%lld",ans),0;
}
Postscript
只会四题的我被所有人吊打实在是太菜了