2024暑假集训测试8
前言
- 比赛链接。
爆零了?!?T4 莫名 CE 了,T2 因为某些人打乱搞做法使出题人改数据和时限,\(O(npk)\) 做法死掉了,主要还是数组开大了还忘了算,直接爆零了。
T1 White and Black
显然不存在无解,从根开始扫,遇到黑色就翻转,前后顺序不影响结果,该方案为正确且唯一方案。
继续观察发现若一个点与其父亲颜色不同,则产生一次贡献。
答案为 \(m+\sum\limits_{i=1}^{m}size_i-sum_i\),\(sum_i\) 指其子节点中颜色同样为黑色的个数。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,fa[N],deg[N],b[N],ans;
vector<int>e[N];
signed main()
{
read(n),read(m);
for(int i=2;i<=n;i++) read(fa[i]),deg[fa[i]]++;
for(int i=1,x;i<=m;i++)
{
read(x); ans=x;
for(int j=1;j<=x;j++) read(b[j]),deg[fa[b[j]]]-=2;
for(int j=1;j<=x;j++) ans+=deg[b[j]];
for(int j=1;j<=x;j++) deg[fa[b[j]]]+=2;
write(ans),puts("");
}
}
T2 White and White
-
部分分 \(10pts\):\(O(n^2k)~DP\)。
-
部分分 \(50pts\):\(O(nkp)\),考虑对于每个前缀和同余相同的本质是相同的,对于每个模数只保存最优策略即可。
本来能过的做法。 -
部分分 \(50pts\):\(O(nk\log(p))\),树状数组做法,本来是能过的,但赛时出题人发现某人该做法常数巨大十分不爽,改时限就过不去了;又因为某人直接输出 \(sum\bmod p\) 还贼不好卡,又把捆绑加上了。
-
正解:
先写出 \(O(n^2k)\) 的转移方程:
\[f_{i,k}=\min\limits_{j=k-1}^{i-1}\{f_{j,k-1}+(sum_i-sum_j)\bmod p\} \]发现对于每个 \(f_{i,*}\) 其 \(\bmod p\) 的值恒等于 \(sum_i \bmod p\),那么对于两个转移点 \(x,y\),有:
\[f_{x,k-1}+(sum_i-sum_x)\bmod p\equiv f_{y,k-1}+(sum_i-sum_y)\bmod p\pmod p \]若 \(f_{x,k-1}<f_{y,k-1}\),因为 \((sum_i-sum_j)\bmod p<p\),所以一定有 \(f_{x,k-1}+(sum_i-sum_x)\bmod p\le f_{y,k-1}+(sum_i-sum_y)\bmod p\)。
所以直接记录最优策略即可,复杂度 \(O(nk)\)。
点击查看代码
#include<bits/stdc++.h> #define int long long #define endl '\n' #define sort stable_sort using namespace std; const int N=5e5+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,p,f[N][2],sum[N],lastx[N],lasty[N]; queue<pair<int,int>>q; signed main() { read(n),read(m),read(p); for(int i=1,a;i<=n;i++) read(a),sum[i]=sum[i-1]+a; memset(f,0x3f,sizeof(f)); f[0][0]=0; for(int j=1;j<=m;j++) { int minn=f[j-1][(j-1)&1],ans=j-1; for(int i=j;i<=n;i++) { f[i][j&1]=minn+(sum[i]-sum[ans])%p; if(f[i][(j-1)&1]<minn) { minn=f[i][(j-1)&1]; ans=i; } } } write(f[n][m&1]); }
T3 Black and Black
直接填入 \(1\sim n\),若 \(sum=0\),直接输出即可。
若 \(sum<0\),设 \(v=0-sum\) 因为现在是满足单调递增的,要维持这个转台只能从前缀或后缀修改,那么对于一个 \(a_i\) 的前缀和与后缀和,若其存在负值前缀和,定存在前缀和为 \(-1\) 的 \(i\),另 \(1\sim i\) 全部 \(-v\)即可;同理若存在正值后缀和,定存在后缀和为 \(1\) 的 \(i\),另 \(i\sim n\) 全部 \(+v\) 即可。
反之同理。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,a[N],sum,ans[N],pre[N],suf[N];
signed main()
{
read(n);
for(int i=1;i<=n;i++)
read(a[i]),
sum+=a[i]*i,
pre[i]=pre[i-1]+a[i],
ans[i]=i;
for(int i=n;i>=1;i--)
suf[i]=suf[i+1]+a[i];
if(sum==0)
{
puts("Yes");
for(int i=1;i<=n;i++) write(i),putchar(' ');
}
if(sum<0)
{
int v=0-sum;
bool flag=0;
for(int i=1;i<=n;i++)
if(pre[i]==-1)
{
for(int j=1;j<=i;j++) ans[j]-=v;
flag=1;
break;
}
if(!flag)
for(int i=n;i>=1;i--)
if(suf[i]==1)
{
for(int j=n;j>=i;j--) ans[j]+=v;
flag=1;
break;
}
if(!flag) puts("No");
else
{
puts("Yes");
for(int i=1;i<=n;i++) write(ans[i]),putchar(' ');
}
}
if(sum>0)
{
int v=sum-0;
bool flag=0;
for(int i=1;i<=n;i++)
if(pre[i]==1)
{
for(int j=1;j<=i;j++) ans[j]-=v;
flag=1;
break;
}
if(!flag)
for(int i=n;i>=1;i--)
if(suf[i]==-1)
{
for(int j=n;j>=i;j--) ans[j]+=v;
flag=1;
break;
}
if(!flag) puts("No");
else
{
puts("Yes");
for(int i=1;i<=n;i++) write(ans[i]),putchar(' ');
}
}
}
T4 Black and White
-
部分分 \(60pts\):\(O(nm)\) 暴力。
-
正解:
这道题方法特别多,我写的是括号序列的做法。
括号序列指遍历一棵树的过程中进入一个节点前加一个
(
,将该节点子树遍历完后加一个)
,从而与原 \(dfs\) 序得到一个括号序列。那么此时对于两点间的距离,截取两点之间的区间,将能够匹配上的左右括号删去,剩下的括号的个数就是两点间距离,很好理解,局限性是只能处理边权为 \(1\) 的情况。
考虑对于动态修改的黑色点集维护树的直径,开一颗线段树,每个节点即维护对应区间 \([l,r]\) 内的直径,考虑如何维护。
定义 \(suml,sumr\) 表示左右括号个数,每次合并左区间左括号与右区间右括号匹配,故有:
if(t[ls].suml>t[rs].sumr) { f.suml=t[ls].suml-t[rs].sumr+t[rs].suml; f.sumr=t[ls].sumr; } else { f.suml=t[rs].suml; f.sumr=t[ls].sumr+t[rs].sumr-t[ls].suml; }
类似于区间子段和的操作,找到直径最大的一个子区间,故此需要维护前后缀,假设找到了那个子区间,有 \(f.dis=ls.sumr+|ls.suml-rs.sumr|+rs.suml\),故有 \(f.dis=\max((ls.suml+ls.sumr)+(rs.suml-rs.sumr),(ls.sumr-ls.suml)+(rs.suml+rs.sumr))\),不妨另 \(pre1,pre2\) 分别表示前缀 \(\max(suml+sumr),\max(suml-sumr)\),\(suf1,suf2\) 分别表示后缀的 \(\max(suml+sumr),\max(sumr-suml)\),从而有转移:
f.dis=max({t[ls].dis,t[rs].dis,t[ls].suf1+t[rs].pre2,t[ls].suf2+t[rs].pre1}); f.pre1=max({t[ls].pre1,t[rs].pre1+t[ls].sumr-t[ls].suml,t[rs].pre2+t[ls].suml+t[ls].sumr}); f.pre2=max({t[ls].pre2,t[rs].pre2+t[ls].suml-t[ls].sumr}); f.suf1=max({t[rs].suf1,t[ls].suf1+t[rs].suml-t[rs].sumr,t[ls].suf2+t[rs].suml+t[rs].sumr}); f.suf2=max({t[rs].suf2,t[ls].suf2+t[rs].sumr-t[rs].suml});
对于白点是没有意义的,另起 \(dis,pre,suf\) 均为 \(-inf\) 即可。
修改直接单点修改即可。
点击查看代码
#include<bits/stdc++.h> #define ll long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls (p<<1) #define rs (p<<1|1) using namespace std; const int N=5e5+10,inf=1e9; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } template<typename Tp> inline void wt(Tp x) {if(x>9)wt(x/10);putchar((x%10)+'0');} template<typename Tp> inline void write(Tp x) {if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,tot,cnt,pos[N],dfn[N],c[N]; vector<int>e[N]; struct aa {int l,r,dis,suml,sumr,pre1,pre2,suf1,suf2;}t[N<<2]; void dfs(int x,int fa) { pos[++tot]=-1; pos[++tot]=x; dfn[x]=tot; for(int y:e[x]) if(y!=fa) dfs(y,x); pos[++tot]=-2; } void push(int p,int x) { f.suml=f.sumr=0; f.dis=f.pre1=f.pre2=f.suf1=f.suf2=-inf; if(pos[x]==-1) f.suml=1; else if(pos[x]==-2) f.sumr=1; else if(c[pos[x]]==0) f.dis=f.pre1=f.pre2=f.suf1=f.suf2=0; } void pushup(int p) { if(t[ls].suml>t[rs].sumr) { f.suml=t[ls].suml-t[rs].sumr+t[rs].suml; f.sumr=t[ls].sumr; } else { f.suml=t[rs].suml; f.sumr=t[ls].sumr+t[rs].sumr-t[ls].suml; } f.dis=max({t[ls].dis,t[rs].dis,t[ls].suf1+t[rs].pre2,t[ls].suf2+t[rs].pre1}); f.pre1=max({t[ls].pre1,t[rs].pre1+t[ls].sumr-t[ls].suml,t[rs].pre2+t[ls].suml+t[ls].sumr}); f.pre2=max({t[ls].pre2,t[rs].pre2+t[ls].suml-t[ls].sumr}); f.suf1=max({t[rs].suf1,t[ls].suf1+t[rs].suml-t[rs].sumr,t[ls].suf2+t[rs].suml+t[rs].sumr}); f.suf2=max({t[rs].suf2,t[ls].suf2+t[rs].sumr-t[rs].suml}); } void build(int p,int l,int r) { f.l=l,f.r=r; if(l==r) {push(p,l); return ;} int mid=(l+r)>>1; build(ls,l,mid),build(rs,mid+1,r); pushup(p); } void change(int p,int x) { if(f.l==f.r) {push(p,x); return ;} int mid=(f.l+f.r)>>1; if(x<=mid) change(ls,x); else change(rs,x); pushup(p); } signed main() { read(n); for(int i=1,a,b;i<=n-1;i++) { read(a),read(b); e[a].push_back(b); e[b].push_back(a); } dfs(1,0); build(1,1,tot); read(m); cnt=n; for(int i=1,x;i<=m;i++) { char op; cin>>op; if(op=='C') { read(x); c[x]^=1; cnt+=(c[x]==0)?1:-1; change(1,dfn[x]); } else { if(cnt==0) puts("-1"); else if(cnt==1) puts("0"); else write(t[1].dis),puts(""); } } }
总结
不管什么情况记得算空间。
由于赛时被 Huge
喊回去整理内务耗费时间,但不是主要原因,因为身体不好走两趟直接虚了,状态很差。
昨天 #define int long long
被 \(D\) 了,以后不这么写了。