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;
}