JZOJ5853. 【NOIP2018提高组模拟9.6】老大

本文探讨了一种解决特定树状结构问题的算法:如何通过断开一条边将树分割成两部分,使得这两部分的最长直径达到最小。文章详细解释了为何断开位置应在原始树的直径上,并介绍了如何计算分割后各部分的直径长度,最终找到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述
这里写图片描述

题解

如果只放一个点,很显然就是放在直径的中点上面,这样一定是最优的,
而现在题目要求断开一条边,然后使得两个部分的最长直径最短。
考虑断开这条边的位置,一定是在原来那棵树的直径上面。
于是呢,就把直径抽出来,
求出断开每一条边的上半部分跟下半部分的直径分别是多少,
然后组合一下就好了。

code

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#include <time.h>
#define ll long long
#define N 13
#define M 103
#define db double
#define P putchar
#define G getchar
#define inf 998244353
#define pi 3.1415926535897932384626433832795
using namespace std;
char ch;
void read(int &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

int a[N][N],b[N][N],ans[N][N],num;
int bel[N][N],s[N*N],si[N*N],m,n;
int fx[4][2]={{-1,0},{0,-1},{0,1},{1,0}};
bool t1[N][10],t2[N][10];

struct node
{
    int x,y,si,cnt;
}w[N*N];

bool cmp(node a,node b) 
{
    return a.si<b.si || (a.si==b.si && a.cnt<b.cnt);
}

void dfs(int x,int y,int t)
{
    int xx,yy;
    for(int i=0;i<4;i++)
    {
        xx=x+fx[i][0];
        yy=y+fx[i][1];
        if(xx<1 || yy<1 || xx>n ||yy>n)continue;
        if(!bel[xx][yy] && a[xx][yy]==t)si[bel[xx][yy]=bel[x][y]]++,dfs(xx,yy,t);
    }
}

bool check()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(ans[i][j]<b[i][j])return 0;
            if(ans[i][j]>b[i][j])return 1;
        }
    return 0;
}

void dg(int now)
{
    int id,x=w[now].x,y=w[now].y;
    if(now>n*n)
    {
        num++;
        if(check())memcpy(ans,b,sizeof(ans));
        return;
    }
    for(int i=1;i<10;i++)
        if(t1[x][i] && t2[y][i])
        {
            id=bel[x][y];
            if(s[id]%i)continue;
            if(si[id]==1 && (s[id]^i))continue;
            si[id]--;s[id]=s[id]/i;
            b[x][y]=i;t1[x][i]=t2[y][i]=0;
            dg(now+1);
            si[id]++;s[id]=s[id]*i;
            t1[x][i]=t2[y][i]=1;
        }
}

int main()
{
    freopen("kenken.in","r",stdin);
    freopen("kenken.out","w",stdout);

    read(n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            read(a[i][j]);

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(!bel[i][j])
            {
                s[bel[i][j]=++m]=a[i][j];
                si[m]=1;
                dfs(i,j,a[i][j]);
            }
            w[i*n-n+j].x=i;
            w[i*n-n+j].y=j;
            w[i*n-n+j].si=a[i][j]; 
            w[i*n-n+j].cnt=bel[i][j]; 
        }

    sort(w+1,w+1+n*n,cmp);
    memset(t1,1,sizeof(t1));
    memset(t2,1,sizeof(t2));

    ans[1][1]=N,dg(1);
    write(num);P('\n');
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            write(ans[i][j]),P(' ');
        P('\n');
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值