暑假集训 加赛1

暑假集训 加赛1

组题人: @Chen_jr

T1 P145 修仙(restart)

  • 原题: 2022NOIP A层联测13 T4 修仙(restart)

  • 题目中的 可以恰好

  • 考虑计算双休带来的差值。

  • bi=ai+ai+1max(ai+ai+1,0) ,则需要计算从 [1,n) 中选出 k 个不相邻的 bi 的最大价值,同 luogu P3620 [APIO/CTSC2007] 数据备份 | CF958E2 Guard Duty (medium) | SP1553 BACKUP - Backup Files | luogu P1484 种树 | luogu P1792 [国家集训队] 种树

  • 然后就是反悔贪心板子了。

    • 基本思想是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项,常写作两种决策之间作差的形式;否则,正式接受。如此往复。
    • 容易有在求解最大值/最小值的最优解中,最大值/最小值左右两端的数要么都选要么都不选。
    • k 的状态能由 k1 的状态继承而来,启发我们可以依次处理子问题。
    • 算法流程:先选出 {b} 中的最大值 bpos 加入答案集合,然后删除 bpos1,bpos+1 ,用 bpos1+bpos+1bpos 替代原来的 bpos
      • 若选了 bpos1+bpos+1bpos 等价于将原来答案集合里的 bpos 替代成 bpos1+bpos+1
      • 否则选择 bpos
    • 双向链表维护动态插入和删除;优先队列维护每次选出最大值,需要懒惰删除法。
    • 边界 0n+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 ,则 (xsiz)2×siz(siz1)2 即为所求。
  • 现在问题来到了怎么求最大子图/团,常采用 Bron–Kerbosch 算法或折半搜索。
    • Bron–Kerbosch 算法
      • 维护 R/P/X 三个集合,分别表示当前正在找的极大团里的点/有可能加入当前正在找的极大团里的点/已经找到的极大团内的点。

        • X 用于判断找到的团先前有没有找过,在统计最大团方案数时需要用到。
      • 算法流程如下

        • (1) 初始化 R,X 为空集, P 为包含所有节点的集合。
        • (2)P 中一点 u ,设 Qu 表示所有与 u 有边相邻的点的集合,递归集合 Ru,PQu,xQu 。递归过程中若出现 P,X 均为空的情况,则 P 内部的点就构成了一个极大子团,统计答案。
          • 要保证加入 u 后仍是团,必须保证加入后 P 中所有点都与 u 相连,故取并集。
            • 这实际上是设定关键点优化的一部分。
        • (3)uP 中删除,并加入到 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 中的所有点,维护 fS 表示集合 S 中所有点组成子图中最大团大小。

        之后枚举 B 中的最大团 T ,找到最大团中所有节点向 S 集合连边的交集,显然 popcount(T)+fS 可以更新答案。

总结

  • 科普场。
  • 反悔贪心是去年听 Accoder 的多校联训的时候写的,只学了写法,没学怎么将题面转化成反悔贪心可解决的问题,导致 T1 没看出来是反悔贪心。

后记

  • 成分复杂。

posted @   hzoi_Shadow  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
扩大
缩小
点击右上角即可分享
微信分享提示