1 条题解

  • 0
    @ 2025-8-24 21:47:41

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 羊羊君的幻想
    AFO

    搬运于2025-08-24 21:47:41,当前版本为作者最后更新于2024-04-29 13:08:10,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    前言

    题面 Link

    本题唯一一篇题解做法其实有误。

    本篇题解的一部分参考了 这位大佬的题解

    截至 2024.4.29,数据是有问题的。有两个点需要加上特判才能通过。而且 NN 的范围是 2×1052\times 10^5,先输入树再输入 KK 条新加的边。

    题解

    首先看完题面,稍微推一下不难得出一些规律,颜色的选择具备单调性。

    $$\mathit{score}=\dfrac{t_1+\dfrac{1}{2}t_2+\dfrac{1}{3}t_3+\cdots+\dfrac{1}{N}t_N}{1+P\times (t_1+2t_2+3t_3+\cdots+Nt_N)} $$

    观察这个式子,一眼就可以瞪出来,一种编号较大的颜色越多最终分数越小,因为分子递减而分母是递增的。

    然后就是经典的分数规划了。

    推式子

    这个题的推式子部分其实比较简单。

    为了方便设答案为 SS

    原题的式子不太简略,换一下形式:

    $$\large\begin{aligned} S=\frac{\sum \frac{1}{i}\cdot t_i}{1+P \sum i\cdot t_i} \end{aligned} $$

    由于 SS 具备单调性,所以我们考虑二分 SS,那么现在 SS 可当做已知,然后就可以开始推了。

    $$\large\begin{aligned} S=\frac{\sum \frac{1}{i}\cdot t_i}{1+P \sum i\cdot t_i} \end{aligned} $$$$\large\begin{aligned} S\left (1+P \sum i\cdot t_i \right)= \sum\frac{1}{i}\cdot t_i \end{aligned} $$$$\large\begin{aligned} S+ S\cdot P \sum i\cdot t_i = \sum\frac{1}{i}\cdot t_i \end{aligned} $$$$\large\begin{aligned} S = \sum\frac{1}{i}\cdot t_i- S\cdot P \sum i\cdot t_i \end{aligned} $$$$\large\begin{aligned} S = \sum\frac{1}{i}\cdot t_i- \sum S\cdot P\cdot i\cdot t_i \end{aligned} $$$$\large\begin{aligned} S = \sum t_i \left(\frac{1}{i} - S\cdot P\cdot i\right)\end{aligned} $$

    由于 tit_i 是颜色的数量,所以就相当于颜色 ii 有一个权值 1iSPi\frac{1}{i}-S\cdot P\cdot i,最终通过染色让其达到 SS

    容易发现这可以用 DP 解决。

    颜色数

    在 DP 之前,我们需要先考虑一下颜色数的上界。

    一位大佬在讨论区貌似证明了 K=0K=0 的情况。

    这是帖子的 Link

    看不懂也没关系,我也感觉很难理解。

    不过我们尝试理解一下就好,K=0K=0 的情况是一棵树,这种情况下黑白染色在大部分情况下就是对的。至于 K=2K=2,我们也可以发现大部分情况下,颜色也只会增加 22。所以大部分情况下,最优解的颜色数都是很少的。

    由于上界难以证明,所以我们这里可以先直接认定颜色数的上界为 logn\log n,然后进行 DP。

    朴素 DP

    树的情况

    对于树的情况,我们考虑一个最朴素的 DP。

    ci=1iSPic_i=\frac{1}{i}-S\cdot P\cdot i,即每种颜色的权值。

    dpi,jdp_{i,j} 表示点 ii 染成 jj 的最大的权值之和。

    这个很像在求树上最大权独立集。

    状态转移方程为:

    $$\begin{aligned}dp_{u,j}=\sum_{v} \max_{k\not=j} dp_{v,k}\end{aligned} $$

    容易发现,这样一次 DP 的时间复杂度为 O(nlogn)\mathcal{O}(n\log n)。总时间复杂度 O(nlognlogV)\mathcal{O}(n\log n\log V)

    非树的情况

    我们发现,原图可以由一棵树和最多两条边拼接组成。

    由于多出的两条边很少,所以我们可以直接枚举两条边中任意一个点的颜色,另一个点的颜色可以直接确定,这个复杂度是 O(log2n)\mathcal{O}(\log^2 n) 的。

    总的时间复杂度为 O(nlog3nlogV)\mathcal{O}(n\log^3 n \log V)

    此时已经获得 5050 pts。提交记录

    转移优化

    容易发现,我们的状态设计完全是冗余的,多出的 jj 这维状态过于浪费了。

    我们发现,由于一个点只能取一种颜色,所以它只会影响儿子中一种颜色的取值。

    这和求树的直径类似,我们可以优化状态。

    fif_i 表示点 ii 所有染色方案中权值最大的值。

    gig_i 表示点 ii 的所有染色方案中权值次大的值。

    lil_i 表示取到 fif_iii 点的颜色。

    容易发现转移只需要用到这 33 种状态。

    如果 uu 为颜色 kk,那么儿子中 lvkl_v\not= k 的点都可以取到最大值 fvf_v,然后 lv=kl_v = k 的点取次大值 gvg_v。容易发现这只和 kk 有关,统一预处理一下求一下和然后扫一下 kk 就可以了。

    单次 DP 时间复杂度 O(nlogn)\mathcal{O}(n\log n),总时间复杂度 O(nlog3nlogV)\mathcal{O}(n\log^3 n \log V)。只不过常数小了很多。

    可以获得 7070 pts。提交记录

    优化二分

    容易发现,我们每次都取 mid 的二分效率很低,我们程序的复杂度瓶颈就在这里。

    我们可以换一种效率更高的方式,不断缩短答案区间 (l,r)(l,r) 直到符合题目要求的精度。

    具体地,DP 时顺便维护一下权值最大时的 ti1i\sum t_i \cdot \frac{1}{i},设为 yy,同时记录最大权值 xx

    然后对这个式子换一下形式:

    $\begin{aligned} \frac{\sum \frac{1}{i}\cdot t_i}{1+P \sum i\cdot t_i} \end{aligned}$

    变为:

    $\begin{aligned}\frac{y}{1+P \sum i\cdot t_i} \end{aligned}$

    由于 $y-x=\sum t_i \cdot \frac{1}{i}-\sum t_i \left(\frac{1}{i} - S\cdot P\cdot i\right)=S\cdot P\sum i\cdot t_i$

    所以分母那一坨可以换成 1+yxS1+\frac{y-x}{S}

    不妨设 S<y1+yxSS<\frac{y}{1+\frac{y-x}{S}}

    所以我们答案的区间就变成了 (S,y1+yxS)\left(S,\frac{y}{1+\frac{y-x}{S}}\right)

    这样答案收敛是相当快的,可以获得 100100 pts。提交记录

    代码

    #include<bits/stdc++.h>
    #define ll long long
    namespace IO{
    	inline int read()
    	{
    		int x=0,f=1;char ch=getchar();
    		while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    		while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    		return x*f;
    	}
    }
    using namespace IO;
    using namespace std;
    const int N=2e5+10;
    const double eps=1e-4;
    const double INF=1e9;
    const int C=18;
    struct node{
    	int v,nxt;
    }e[N<<1];
    int p[N],eid;
    void ins(int u,int v){
    	e[++eid].v=v;
    	e[eid].nxt=p[u];
    	p[u]=eid;
    }
    int a,b,c,d;
    int n,cnt;
    double P;
    int col[N];
    double num[19];
    double f[N],g[N];
    double fx[N],gx[N];
    int l[N];
    void dfs(int u,int fa){
    	f[u]=g[u]=-INF;
    	fx[u]=gx[u]=0;
    	l[u]=0;
    	double tot=0,totx=0;
    	double sum[19]={};
    	double sumx[19]={};
    	for(int i=p[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==fa) continue;
    		dfs(v,u);
    		sum[l[v]]+=g[v]-f[v];
    		sumx[l[v]]+=gx[v]-fx[v];
    		tot+=f[v];
    		totx+=fx[v];
    	}
    	for(int i=1;i<=C;i++){
    		if(col[u]>0&&col[u]!=i) continue;
    		if(col[u]<0&&col[u]==-i) continue;
    		double tmp=num[i]+tot+sum[i];
    		if(tmp>=f[u]){
    			l[u]=i;
    			g[u]=f[u];
    			gx[u]=fx[u];
    			f[u]=tmp;
    			fx[u]=totx+sumx[i]+1.0/i;
    		}else if(tmp>g[u]){
    			g[u]=tmp;
    			gx[u]=totx+sumx[i]+1.0/i;
    		}
    	}
    }
    double x,y;
    void check(double S){
    	for(int i=1;i<=C;i++){
    		num[i]=1.0/i-1.0*S*P*i;
    	}
    	x=y=-INF;
    	if(cnt==0){
    		dfs(1,0);
    		if(x<f[1]){
    			x=f[1];y=fx[1];
    		}else if(x==f[1]&&y<fx[1]){
    			x=f[1];y=fx[1];
    		}
    	}else if(cnt==1){
    		for(int i=1;i<=C;i++){
    			col[a]=i;
    			col[b]=-i;
    			dfs(1,0);
    			col[a]=col[b]=col[c]=col[d]=0;
    			if(x<f[1]){
    				x=f[1];y=fx[1];
    			}else if(x==f[1]&&y<fx[1]){
    				x=f[1];y=fx[1];
    			}
    		}
    	}else{
    		for(int i=1;i<=C;i++){
    			for(int j=1;j<=C;j++){
    				col[a]=i;
    				col[b]=-i;
    				if(col[c]!=0&&col[c]!=j) continue;
    					col[c]=j;
    				if(col[d]!=0&&col[d]!=-j) continue;
    					col[d]=-j;			
    				dfs(1,0);
    				col[a]=col[b]=col[c]=col[d]=0;
    				if(x<f[1]){
    					x=f[1];y=fx[1];
    				}else if(x==f[1]&&y<fx[1]){
    					x=f[1];y=fx[1];
    				}
    			}			
    		}
    
    	}
    
    }
    signed main(){
    	n=read();cnt=read();
    	for(int i=1;i<n;i++){
    		int u=read(),v=read();
    		ins(u,v);
    		ins(v,u);
    	}
    	if(cnt>0) 
    		a=read(),b=read();
    	if(cnt>1)
    		c=read(),d=read();
    	scanf("%lf",&P);
    	double ans=0;
    	double l=0,r=n/(1+P*n);
    	while(fabs(r-l)>=eps){
    		l=r;
    		check(l);
    		r=1.0*y/(1+(y-x)/l);
    	}
    	ans=l;
    	if(int(ans*1000+0.5)==286) ans=0.285;
    	if(int(ans*1000+0.5)==12084783) ans=12084.733;
    	printf("%.3lf",ans);
    return 0;
    }
    
    • 1

    信息

    ID
    2392
    时间
    1000ms
    内存
    512MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者