暑假集训 加赛1
暑假集训 加赛1
组题人: @Chen_jr
\(T1\) P145 修仙(restart)
-
题目中的 可以 指 恰好 。
-
考虑计算双休带来的差值。
-
令 \(b_{i}=a_{i}+a_{i+1}-\max(a_{i}+a_{i+1},0)\) ,则需要计算从 \([1,n)\) 中选出 \(k\) 个不相邻的 \(b_{i}\) 的最大价值,同 luogu P3620 [APIO/CTSC2007] 数据备份 | CF958E2 Guard Duty (medium) | SP1553 BACKUP - Backup Files | luogu P1484 种树 | luogu P1792 [国家集训队] 种树。
-
然后就是反悔贪心板子了。
- 基本思想是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项,常写作两种决策之间作差的形式;否则,正式接受。如此往复。
- 容易有在求解最大值/最小值的最优解中,最大值/最小值左右两端的数要么都选要么都不选。
- \(k\) 的状态能由 \(k-1\) 的状态继承而来,启发我们可以依次处理子问题。
- 算法流程:先选出 \(\{ b \}\) 中的最小值 \(b_{pos}\) 加入答案集合,然后删除 \(b_{pos-1},b_{pos+1}\) ,用 \(b_{pos-1}+b_{pos+1}-b_{pos}\) 替代原来的 \(b_{pos}\) 。
- 若选了 \(b_{pos-1}+b_{pos+1}-b_{pos}\) 等价于将原来答案集合里的 \(b_{pos}\) 替代成 \(b_{pos-1}+b_{pos+1}\) 。
- 否则选择 \(b_{pos}\) 。
- 双向链表维护动态插入和删除;优先队列维护每次选出最小值,需要懒惰删除法。
- 边界 \(0\) 和 \(n+1\) 一般需要特殊处理。
- 实际上是对 \(1,n\) 的处理。
点击查看代码
priority_queue<pair<ll,ll> >q; ll a[200010],b[200010],cha[200010],l[200010],r[200010],vis[200010]; int main() { ll n,x,k,ans=0,pos,i; cin>>n>>x; k=n/2; for(i=1;i<=n;i++) { cin>>a[i]; ans+=a[i]; } for(i=1;i<=n-1;i++) { b[i]=a[i]+a[i+1]-max(a[i]+a[i+1]-x,0ll); l[i]=i-1; r[i]=i+1; q.push(make_pair(b[i],i)); } b[0]=b[n]=-0x3f3f3f3f3f3f3f3f;//保证不会被选到 for(i=1;i<=k;i++) { while(q.empty()==0&&vis[q.top().second]==1) { q.pop(); } if(q.empty()==0) { ans-=q.top().first; pos=q.top().second; q.pop(); vis[l[pos]]=vis[r[pos]]=1; b[pos]=b[l[pos]]+b[r[pos]]-b[pos]; q.push(make_pair(b[pos],pos)); l[pos]=l[l[pos]]; r[pos]=r[r[pos]]; l[r[pos]]=r[l[pos]]=pos; } cout<<ans<<endl; } return 0; }
\(T2\) T3682. 七负我
- 不难发现,完全子图对答案的贡献最大。
- 设最后得到的最大子图/团大小为 \(siz\) ,则 \((\frac{x}{siz})^{2} \times \frac{siz(siz-1)}{2}\) 。
- 现在问题来到了怎么求最大子图/团,常采用 Bron–Kerbosch 算法或折半搜索。
- Bron–Kerbosch 算法
-
维护 \(R/P/X\) 三个集合,分别表示当前正在找的极大团里的点/有可能加入当前正在找的极大团里的点/已经找到的极大团内的点。
- \(X\) 用于判断找到的团先前有没有找过,在统计最大团方案数时需要用到。
-
算法流程如下
- \((1)\) 初始化 \(R,X\) 为空集, \(P\) 为包含所有节点的集合。
- \((2)\) 取 \(P\) 中一点 \(u\) ,设 \(Q_{u}\) 表示所有与 \(u\) 有边相邻的点的集合,递归集合 \(R \bigcup u,P \bigcap Q_{u},x \bigcap Q_{u}\) 。递归过程中若出现 \(P,X\) 均为空的情况,则 \(P\) 内部的点就构成了一个极大子团,统计答案。
- 要保证加入 \(u\) 后仍是团,必须保证加入后 \(P'\) 中所有点都与 \(u\) 相连,故取并集。
- 这实际上是设定关键点优化的一部分。
- 要保证加入 \(u\) 后仍是团,必须保证加入后 \(P'\) 中所有点都与 \(u\) 相连,故取并集。
- \((3)\) 将 \(u\) 从 \(P\) 中删除,并加入到 \(X\) 中。
- \((4)\) 重复 \((2),(3)\) 操作直至 \(P\) 为空。
-
优化
- 及时剪枝
- 在开始时把所有点排序,枚举时按照下标顺序,防止重复
- 设定关键点
- 若将 \(u\) 加入 \(R\) 后,再取与 \(u\) 有边相连的点 \(v\) 加入 \(R\) 后仍是一个极大团,那么加入 \(u,v\) 的顺序并不影响答案。
- 所以将 \(u\) 加入 \(R\) 后,再取出与 \(u\) 没有边相连的点进行递归。
点击查看代码
int dis[50][50],R[50][50],P[50][50],X[50][50],siz=0; void Bron_Kerbosch(int dep,int r,int p,int x)//dep 表示当前搜到第几层 { if(p==0&&x==0)//若 x 不等于 0 说明这个团之前已经算过了,才能用来统计方案数 { siz=max(siz,r); } else { int u=P[dep][1]; for(int i=1;i<=p;i++) { if(dis[u][P[dep][i]]==0)//因为 dis[P[dep][1]][P[dep][1]]=0 所以一开始会算一次 { for(int j=1;j<=r;j++)//继承 { R[dep+1][j]=R[dep][j]; } R[dep+1][r+1]=P[dep][i];//加入 int np=0,nx=0; for(int j=1;j<=p;j++) { if(dis[P[dep][i]][P[dep][j]]!=0) { np++; P[dep+1][np]=P[dep][j];//取并集 } } for(int j=1;j<=x;j++) { if(dis[P[dep][i]][X[dep][j]]!=0) { nx++; X[dep+1][nx]=X[dep][j];//取并集 } } Bron_Kerbosch(dep+1,r+1,np,nx); x++; X[dep][x]=P[dep][i];//加入 P[dep][i]=0;//删除 } } } } int main() { int n,m,x,u,v,i; cin>>n>>m>>x; for(i=1;i<=m;i++) { cin>>u>>v; dis[u][v]=dis[v][u]=1; } for(i=1;i<=n;i++) { P[1][i]=i; } Bron_Kerbosch(1,0,n,0); printf("%.6lf\n",(1.0*x/siz)*(1.0*x/siz)*siz*(siz-1)/2); return 0; }
-
- 折半搜索
- 以下部分贺的官方题解,改了下 \(\LaTeX\) 。
我们将所有点分为两个集合 \(A,B\) ,对于 \(A\) 中的所有点,维护 \(f_{S}\) 表示集合 \(S\) 中所有点组成子图中最大团大小。
之后枚举 \(B\) 中的最大团 \(T\) ,找到最大团中所有节点向 \(S\) 集合连边的交集,显然 \(\operatorname{popcount}(T)+f_S\) 可以更新答案。
- 以下部分贺的官方题解,改了下 \(\LaTeX\) 。
- Bron–Kerbosch 算法
总结
- 科普场。
- 反悔贪心是去年听 \(Accoder\) 的多校联训的时候写的,只学了写法,没学怎么将题面转化成反悔贪心可解决的问题,导致 \(T1\) 没看出来是反悔贪心。
后记
-
成分复杂。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18315649,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。