2020牛客暑期多校训练营(第七场)A.Social Distancing(计算几何 dp/打表)

题目

T(T<=250)组样例,每次给出一个圆的半径r(r<=30),

在圆上和圆内放置n个整点,要求\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}d(i,j)^{2}的最大值。

其中d(i,j)表示i和j之间的距离。即求所有点的距离的平方和的最大值。

思路来源

https://blog.csdn.net/zhangchizc/article/details/107746793

题解

xy分开考虑,不妨只考虑x这一维

考虑这是一个n*n矩阵的上三角矩阵(略有不同,此处对角线均为0)

对于任意一个数i来说,其与j \neq i的j都出现过一次(i,j)对,代表(x_{i}-x_{j})*(x_{i}-x_{j})

二者乘积是x_{i}(x_{1}+x_{i-1}+...+x_{i+1}+...+x_{n}),把xi这项凑足,同时记sum=\sum_{i=1}^{n}x_{i}

则有每个i对答案的贡献是-x_{i}*(sum-x_{i})

可以理解为平方项二倍展开-2*x_{i}*\sum x_{j}(j \neq i),上三角矩阵只有一半,所以把2除掉

且注意到x_{i}出现在n-1(i,j)对里,平方项贡献是(n-1)x_{i}^{2}

\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(x_{i}-x_{j})^{2}即化简为(n-1)\sum_{i=1}^{n}x_{i}^{2}-\sum_{i=1}^{n}(sum-x_{i})*x_{i}=n*\sum_{i=1}^{n}x_{i}^{2}-sum*sum

感觉这个套路见过很多次了,然而想不到要这么dp,

\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}d(i,j)^{2} =\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(x_{i}-x_{j})^{2}+(y_{i}-y_{j})^{2}

于是就化简为n*\sum_{i=1}^{n}(x_{i}^{2}+y_{i}^2)-(\sum_{i=1}^{n}x_{i})^{2}-(\sum_{i=1}^{n}y_{i})^{2}

令dp[i][j][k]表示选了i个点,sumx=j,sumy=k(把后两项固定住)时的最大第一项

则实际答案为i*dp[i][j][k]-j*j-k*k,具体实现时将点按半径r的增序更新,即可更新ans[i][r],

O(1e9)预处理,O(1)回答,5s还可以吧,要是实在太慢就打表

代码

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=64*64+5,off=300;
//dp[i][j][k]表示选i个点 sumx=j sumy=k时 的 最大的平方项代价之和
int t,n,d,dp[9][610][610],ans[9][32],c;
struct node{
    int d,x,y;
    bool operator<(node &x)const{
        return d<x.d;
    }
}e[N];
int main(){
    for(int x=-32;x<=32;++x){
        for(int y=-32;y<=32;++y){
            e[++c]=node{x*x+y*y,x,y};
        }
    }
    sort(e+1,e+c+1);
    memset(dp,128,sizeof dp);
    dp[0][off][off]=0;
    int now=1;
    for(int r=1;r<=30;++r){
        while(now<=c && e[now].d<=r*r){
            for(int i=1;i<=8;++i){
                for(int j=-r*i;j<=r*i;++j){//i个点 abs(sumx) 不可能超过i*r
                    for(int k=-r*i;k<=r*i;++k){
                        dp[i][j+off][k+off]=max(dp[i][j+off][k+off],dp[i-1][j-e[now].x+off][k-e[now].y+off]+e[now].d);
                    }
                }
            }
            now++;
        }
        for(int i=1;i<=8;++i){
            for(int j=-r*i;j<=r*i;++j){
                for(int k=-r*i;k<=r*i;++k){
                    if(dp[i][j+off][k+off]>0){
                        ans[i][r]=max(ans[i][r],dp[i][j+off][k+off]*i-j*j-k*k);
                    }
                }
            }
        }
    }
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&d);
        printf("%d\n",ans[n][d]);
    }
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值