Educational Codeforces Round 93 (Rated for Div. 2) 简要题解
A
根据“两短边之和大于第三边”,可以直接判断\(a_1,a_2,a_n\)的大小关系即可。
int n,a[N];
int main()
{
int T=read();
while (T--)
{
n=read();
rep(i,1,n) a[i]=read();
sort(a+1,a+1+n);
int x=a[1],y=a[2],z=a[n];
if (x+y>z) puts("-1");
else printf("%d %d %d\n",1,2,n);
}
return 0;
}
B
取一段0的话会使得两段1连在一起,显然会帮助对面得到更多的1,所以贪心策略就是按照极长连续1的长度从大到小贪心的取。
int n,m,a[N];
char s[N];
int main()
{
int T=read();
while (T--)
{
m=0;
scanf("%s",s+1);
n=strlen(s+1);
int cnt=0;
rep(i,1,n)
{
if (s[i]=='0')
{
if (cnt) a[++m]=cnt;
cnt=0;
}
else cnt++;
}
if (cnt) a[++m]=cnt;
sort(a+1,a+1+m);
int ans=0;
for (int i=m;i>=1;i-=2) ans+=a[i];
printf("%d\n",ans);
}
return 0;
}
C
记前缀和为\(S_i\),由题知有\(S_r-S_{l-1}=r-l+1\),移项得\(S_r-r=S_{l-1}-(l-1)\),用一个map记录\(S_i-i\)的值的个数即可。
int n;
map<int,int> mp;
char str[N];
int main()
{
int T=read();
while (T--)
{
n=read();scanf("%s",str+1);
mp.clear();mp[0]=1;
int sum=0;ll ans=0;
rep(i,1,n)
{
sum+=(str[i]-'0');
ans+=mp[sum-i];
mp[sum-i]++;
}
printf("%lld\n",ans);
}
return 0;
}
D
考虑四条边\(a,b,c,d\)满足\(a<b<c<d\),经过一些简单的验证可以得到两个矩形面积和为\(ab+cd\)时最大(即配对的边不会有交叉),于是可以直接dp.
int n1,n2,n3,a[210],b[210],c[210];
long long f[210][210][210];
int main()
{
n1=read();n2=read();n3=read();
rep(i,1,n1) a[i]=read();
rep(i,1,n2) b[i]=read();
rep(i,1,n3) c[i]=read();
sort(a+1,a+1+n1);sort(b+1,b+1+n2);sort(c+1,c+1+n3);
f[0][0][0]=0;
rep(i,0,n1) rep(j,0,n2) rep(k,0,n3)
{
if (i) f[i][j][k]=max(f[i][j][k],f[i-1][j][k]);
if (j) f[i][j][k]=max(f[i][j][k],f[i][j-1][k]);
if (k) f[i][j][k]=max(f[i][j][k],f[i][j][k-1]);
if ((i) && (j)) f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k]+a[i]*b[j]);
if ((i) && (k)) f[i][j][k]=max(f[i][j][k],f[i-1][j][k-1]+a[i]*c[k]);
if ((j) && (k)) f[i][j][k]=max(f[i][j][k],f[i][j-1][k-1]+b[j]*c[k]);
}
long long ans=f[n1][n2][n3];
printf("%lld",ans);
return 0;
}
E
记0类武器有\(n_0\)个,1类武器有\(n_1\)个,我们需要维护出对当前\(n_0+n_1\)个武器中伤害的前\(n_1\)大值以进行加倍,但是要注意如果这些应该被加倍的武器全部是1类的话,就需要拿走一个最小的,同时在剩余的武器中取出一个最大的。使用两个set维护前\(n_1\)大和非前\(n_1\)大,注意到一次修改操作给两个set带来的插入和删除操作是\(O(1)\)个的,于是可以直接维护,具体细节见下。
set<pii> s0,s1;
bool chk()
{
if ((s0.empty()) || (s1.empty())) return 0;
pii x=*s0.begin(),y=*s1.rbegin();
return x.fir<y.fir;
}
int main()
{
int q=read();
ll all=0,sum0=0;
int n10=0,n1=0,n0=0;
while (q--)
{
int tp=read(),d=read(),op=1;
all+=d;
pii now=mkp(d,tp);
if (d<0)
{
d=-d;op=-1;now.fir*=-1;
if (s0.find(now)!=s0.end())
{
s0.erase(now);sum0-=d;
if (now.sec) n10--;
}
else s1.erase(now);
}
else s1.insert(now);
if (tp) n1+=op;
else n0+=op;
//cout << "now " << n1 << " " << (int)s0.size() << " " << all << endl;
while (n1<(int)s0.size())
{
pii x=*s0.begin();s0.erase(x);sum0-=x.fir;
if (x.sec) n10--;
s1.insert(x);
}
while (n1>(int)s0.size())
{
if (s1.empty()) break;
pii x=*s1.rbegin();s1.erase(x);
s0.insert(x);sum0+=x.fir;
if (x.sec) n10++;
}
while (chk())
{
pii x=*s0.begin(),y=*s1.rbegin();
//cout << "change " << x.fir << " " << y.fir << endl;
if (x.sec) n10--;
s0.erase(x);s1.erase(y);
sum0-=x.fir;sum0+=y.fir;
s0.insert(y);s1.insert(x);
}
ll nows=sum0;
if ((n10==n1) && (n1))
{
pii x=*s0.begin();nows-=x.fir;
if (!s1.empty())
{
x=*s1.rbegin();nows+=x.fir;
}
}
printf("%lld\n",nows+all);
}
return 0;
}
F
有一个显然的贪心是:枚举\(k\),然后从位置1开始、每次找一个最小的,满足能形成连续\(k\)个0/1的位置跳过去。
根据调和级数那套理论,对于每个\(k\)如果我们能在处于任何位置时,能够用\(f(k)\)找到下一步的起点,那么总的时间复杂度是\(O(f(k)\times n\log n)\).
首先可以预处理\(nxt_{i,0/1}\)表示从i开始往后走的极长0/1段的长度。之后有一个比较暴力的思路是对当前的起点每次二分一个终点,check的话就需要考虑这个区间内\(nxt_{i,0/1}\)的最大值是否\(\geq k\),时间复杂度是\(O(n\log^2n)\), 赛时实测会TLE.
但是由于我们枚举的\(k\)是单调递增的,所以可以用并查集维护以当前点为起点时的最近终点是谁。这样的话就需要在对\(k\)做完答案后将\(\max(nxt_{i,0/1})=k\)的\(i\)连向下一个位置,于是每次查询终点近乎线性,可以通过。
int n,nxt[N][2],fa[N],a[N];
char s[N];
vi val[N];
int find(int x)
{
if (fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
n=read();
scanf("%s",s+1);
per(i,n,1)
{
if (s[i]!='1') nxt[i][0]=nxt[i+1][0]+1;
if (s[i]!='0') nxt[i][1]=nxt[i+1][1]+1;
a[i]=max(nxt[i][0],nxt[i][1]);
val[a[i]].pb(i);
}
//rep(i,1,n) cout << a[i] << " ";cout << endl;
rep(i,1,n+1) fa[i]=i;
rep(k,1,n)
{
int p=1,cnt=0;
while (p+k-1<=n)
{
int nxt=find(p);
if (nxt<=n)
{
p=nxt+k;cnt++;
}
else break;
}
printf("%d ",cnt);
int len=val[k].size();
rep(i,0,len-1)
{
int x=val[k][i],y=x+1;
int fx=find(x),fy=find(y);
fa[fx]=fy;
}
//rep(i,1,n) cout << find(i) << " ";cout << endl;
}
return 0;
}
G
写完F,读懂G之后就会做了,但是没啥时间写了,着实自闭。
考虑求是否存在一个周长为\(C\)的矩形,后面可以用调和级数的复杂度求出最大因数。
去掉边长为\(y\)的边后其实就是求一个集合\(S=\{a_i-a_j|0\leq i,j\leq n,a_i>a_j\}\)中的元素,记\(b_i\)表示是否有\(a_j=i\),那么有\(c_k=\sum_{i=0}^{x-k}b_ib_{i+k}\),这是一个经典的卷积形式,可以将\(b\)翻转得到\(c_k=\sum_{i=0}^{x-k}b_ib_{x-(i+k)}'\),由于值域不大直接NTT即可。
int n,x,y,a[N],b[N],r[N],f[N],ans[N];
void ntt(int lim,int *a,int typ)
{
rep(i,0,lim-1)
if (i<r[i]) swap(a[i],a[r[i]]);
for (int mid=1;mid<lim;mid<<=1)
{
int gn=qpow(3,(maxd-1)/(mid<<1)),len=(mid<<1);
if (typ==-1) gn=getinv(gn);
for (int sta=0;sta<lim;sta+=len)
{
int g=1;
for (int j=0;j<mid;j++,g=mul(g,gn))
{
int x=a[sta+j],y=mul(a[sta+j+mid],g);
a[sta+j]=add(x,y);a[sta+j+mid]=dec(x,y);
}
}
}
if (typ==-1)
{
int invn=getinv(lim);
rep(i,0,lim-1) a[i]=mul(a[i],invn);
}
}
int calcr(int len)
{
int lim=1,cnt=0;
while (lim<len) {lim<<=1;cnt++;}
rep(i,0,lim-1) r[i]=((r[i>>1]>>1)|((i&1)<<(cnt-1)));
return lim;
}
int main()
{
n=read();x=read();y=read();
rep(i,0,n)
{
int x=read();a[x]=1;
}
rep(i,0,x) b[x-i]=a[i];
int lim=calcr((x+1)<<1);
ntt(lim,a,1);ntt(lim,b,1);
rep(i,0,lim-1) a[i]=mul(a[i],b[i]);
ntt(lim,a,-1);
rep(i,1,x)
{
f[i]=a[x+i];
//if (f[i]) cout << i << " ";
}
//cout << endl;
rep(i,0,M) ans[i]=-1;
rep(i,0,x)
{
if (!f[i]) continue;
int c=(y+i)*2;
//cout << "now " << c << endl;
for (int j=c;j<=M;j+=c) ans[j]=max(ans[j],c);
}
int q=read();
while (q--)
{
int x=read();
printf("%d ",ans[x]);
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步