• 【LibreOJ】#6395. 「THUPC2018」城市地铁规划 / City 背包DP+Prufer序


    【题目】#6395. 「THUPC2018」城市地铁规划 / City
    【题意】给定n个点要求构造一棵树,每个点的价值是一个关于点度的k次多项式,系数均为给定的(a_0,...a_k),求最大价值。(n leq 3000,k leq 10)
    【算法】背包DP+Prufer序
    首先每个点度x的价值g(x)可以暴力预处理。将每个点的度-1后,就不再有树形态这个限制了,只要n个点的度加起来是n-2即可,因为此时只要让所有还原后度不为1的点连通,度为1的叶子节点直接分配。
    问题转化为n-2个大小为x价值为g(x+1)的物品,求容量为n-2的完全背包的最大价值,复杂度(O(n^2))
    这里的背包有个问题,就是大小为0的物品也是有价值的(必须n个点都计算),我的方法是所有价值先减g(1),最后再加n*g(1)。

    构造方案的时候可以用n^2数组记录,也可以一步一步找最优大小退容量(因为是完全背包),每个点向上一个点连边使度不为1的点构成一条链,再从后往前分配叶子节点。
    注意:根节点没有向上的路径,但是为了方便背包DP依然减掉一个度,最后构造方案的时候默认第一个点为根节点不往上连边即可。
    还有要特殊处理n=1和n=2的情况,(0^0=1)

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=3010,MOD=59393;
    int n,kind,as[20],g[maxn],f[maxn];
    int main(){
    	scanf("%d%d",&n,&kind);
    	for(int i=0;i<=kind;i++)scanf("%d",&as[i]);
    	if(n==1){printf("%d %d",n-1,as[0]);return 0;}
    	for(int i=1;i<n;i++){
    		int x=1;
    		for(int j=0;j<=kind;j++){
    			g[i]=(g[i]+x*as[j])%MOD;
    			x=x*i%MOD;
    		}
    		if(i!=1)g[i]-=g[1];//
    	}
    	for(int i=1;i<=n-2;i++){
    		for(int j=i;j<=n-2;j++){
    			f[j]=max(f[j],f[j-i]+g[i+1]);
    		}
    	}
    	printf("%d %d
    ",n-1,f[n-2]+n*g[1]);//
    	int x=n-2,id=0,y=n;
    	while(x){
    		for(int i=1;i<=x;i++)if(f[x-i]+g[i+1]==f[x]){
    			x-=i;
    			if(++id!=1)printf("%d %d
    ",id-1,id);else i++;//
    			for(int j=1;j<i;j++)printf("%d %d
    ",id,y--);
    			break;
    		}
    	}
    	if(n==2)id++;
    	printf("%d %d
    ",id,id+1);//
    	return 0;
    }
    

    有没有发现算法里还有”Prufer序“这一项?很有意思的是,上面推到的结论就是Prufer序的结论。
    从Prufer序的角度来考虑,题目和带标号无根树、点度密切相关,可以想到只需要构造一个Prufer序使得各点度+1的价值最大就行了。
    问题转化为n-2个大小为x价值为g(x+1)的物品,求容量为n-2的完全背包的最大价值,复杂度(O(n^2))

  • 相关阅读:
    sql server 行转列
    java 加密解密WORD文档
    数据库重新连接存储
    MSSQL 删除索引
    WORD添加批注(JAVA)
    JAVA添加WORD文档批注
    MYECLIPSE说明书
    K3二次开发后台表
    css css预处理器
    javascript 视频播放指定的时间段
  • 原文地址:https://www.cnblogs.com/onioncyc/p/9052946.html
Copyright © 2020-2023  润新知