zoukankan      html  css  js  c++  java
  • HDU 6616 Divide the Stones

    题面

    Time limit 3000 ms

    Memory limit 131072 kB

    Special judge Yes

    OS Windows

    中文题意

    (n)块石头分成(k)堆,要求每堆石头的重量之和相等。输出是否可以实现要求。如果可以实现,还要输出每堆石头里每一块石头的重量

    比赛惨状

    赛场上看见的第一反应——dp?不会。然后就先扔给有点思路队友,写另一道主席树(这题)去了。主席树写到一半,想起来题目里有个条件,k是n的因子,然后想到(frac{n}{k})是整数,然后想到幻方,于是和刚这题的队友说了一下可以往幻方的方向思考一下,然后队友表示没有听说过幻方,于是我接着TLE我的主席树,直到结束也没A,两个队友刚的另外两题也没刚出来,包括这题。最后两题惨淡收场。

    比赛结束后补题,补分解质因数这题的队友发现,有个循环变量(i)有个地方忘记加一了……我听着其他队说主席树那题的解法,直骂自己蠢,被最多(169)(k)骗了。

    我的走不通的思路

    我们先令(m=frac{n}{k})。这里有一个结论(貌似这题的解法可以当做这个结论的构造性的证明?)——对于这个分石头的问题,如果石头总重量能被k整除,即每一堆石头重量都是整数,(k|frac{(1+n) imes n}{2}),那么一定存在一个解,使得这(k)堆石头,每堆石头都是(m)块。

    应用这个结论,题意就变成了,让我们把(1)~(n)总共(n)个数字填入(k)(m)列的矩阵中,使得矩阵每一行之和相等。

    设每一堆石头重量(w=frac{(1+n) imes n}{2k}=frac{(1+n) imes m}{2})

    (m)是偶数的情况

    此时(n)是偶数,那么(1)~(n)可以两两配对成(frac{n}{2})对重量为(n+1)的石头对。那么我们就可以把这些石头对两个两个地放进矩阵,填完一行填下一行,填满(k)行就完事了。为了更方便地用代码实现,我们可以蛇形填数字,像下面这样——

    18916
    271015
    361114
    451213

    (m)是奇数的情况

    k是偶数

    由于(k imes m=n),则(n)是偶数,((n+1))是奇数,(w=frac{(1+n) imes m}{2}),由于(m)也是奇数,所以每堆石头重量不是整数,无解。

    k是奇数

    (w=frac{(1+n) imes m}{2})是整数((n+1)是偶数),由上文的结论可知,这是有解的。

    幻方的思路(想复杂了还走不通)
    • 如果(mgeqslant k),就是矩阵在横着的方向更长的情况,那么可以把前(k imes k)个数字按照奇数阶幻方的方式填入矩阵左边(k imes k)的部分。剩下((n-k imes k))个石头数量是偶数,要填入矩阵右边(k)(m-k)列,而(m-k)是偶数(奇数减奇数等于偶数),于是问题转化为(m)是偶数的情况,蛇形填就好。

    • 如果(mleqslant k),就是矩阵竖着的方向更长的情况,先说我xjb搞出来的结论——使用幻方无解。下面的推导感觉价值不大

      先摸索一番,如果是像上一种情况,把前(m imes m)个数字填进矩阵上方部分,那么剩下的数字都大于上方用过的(m^2)个,产生的石头堆超重了。于是想着调整一下,求出幻方以后,将幻方内的数字全部增加一个固定的数,使得幻方内数字的值域正好在([1,n])的正中间,那么填完幻方剩下的数字就可以两两配对了。这就差不多转化成了(m)是奇数、(k)是偶数的情况。下面的推导十分不严谨,漏洞百出……先留坑,以后完善一下吧。

      但和(m)是奇数、(k)是偶数的情况有点区别,这里的总和不是(frac{(1+n) imes n}{2}),有(frac{n-m^2}{2})个数字增大了((m^2+frac{n-m^2}{2})),所以剩下的数字总和变成了$$frac{(1+n) imes n}{2}+frac{n-m^2}{2} imes (m2+frac{n-m2}{2})$$

      [=frac{(1+n) imes n}{2}+frac{n-m^2}{2} imes frac{n+m^2}{2} ]

      [=frac{2n+3n^2-m^2}{4} ]

      现在我们的目的是,把这(n-m^2)个数字分成(k-m)行,每行数字之和相等。那么每行的数字之和应该是

      [w=frac{2n+3n^2-m^2}{4(k-m)} ]

      (k=frac{n}{m})代入,整理 不想整理了 可得

      [w=frac{2nm+3n^2m-m^3}{4n-4m^2} ]

      如果能实现要求,那么(w)还应该满足最基本的条件(w=frac{(1+n) imes m}{2})。于是把这两个联立起来试试(开始xjb搞,各种不严谨)

      [frac{2nm+3n^2m-m^3}{4n-4m^2}=frac{(1+n) imes m}{2} ]

      整理得

      [2nm+3n^2m-m^3==2nm-2m^3+2n^2m-2nm^3 ]

      [n^2==-m^2-2nm^2 ]

      因为(n)(m)都是正数,所以上式不成立。所以这种情况使用幻方无解。

    题解的思路

    先把(m=1)的情况特判了。

    对于(m)是偶数的情况,可以用我上面那个蛇形填的办法。

    对于(m)是奇数的情况,题解的做法有点玄学,还没仔细证明过——

    • 首先把矩阵的最左边三列用([1,3k])这些数填满
    • 然后留下的右边部分就转化成了(m)是偶数的情况,蛇形填。

    看不懂题解,最大的问题在于,左边这三列怎么填……题解也不说,咱也没机会问。

    查了其他很多人的博客,他们都把这步一笔带过了……于是把代码复制下来,手动输入数据,看输出找规律。规律是挺明显的——

    5行的情况

    18 15
    21012
    37 14
    49 11
    56 13

    7行的情况

    11121
    21417
    31020
    41316
    59 19
    61215
    78 18

    第一列顺着写下来,第二列和第三列从下到上,步长为2。还没想过为啥这是对的……先留坑

    另一些思路

    来自集训队队长的博客

    源代码

    这个是题解的思路

    #include<queue>
    #include<cstdio>
    
    int T;
    long long n, k;
    std::queue<int> ma[100010];
    int main()
    {
        // freopen("test.in","r",stdin);
        scanf("%d", &T);
        while(T--)
        {
            scanf("%lld%lld", &n, &k);
            long long sum=(1+n)*n>>1;
            if(k!=1&&k==n||sum%k)
            {
                puts("no");
                continue;
            }
            puts("yes");
            for(int i=1;i<=k;i++)
                while(!ma[i].empty()) ma[i].pop();
            long long m=n/k;
            if(m&1)//m为奇数
            {
                for(int i=1;i<=k;i++) ma[i].push(i);
                int id=k+1,row=k;
                for(;id<=3*k&&id<=n;id++,row-=2)//前3列
                {
                    while(row<1) row+=k;
                    ma[row].push(id);
                    if(id%k==0) row--;
                }
                int delta=1;
                for(row=1;id<=n;id++)//后面的蛇形填
                {
                    if(row<1)
                    {
                        row=1;delta=1;
                    }
                    else if(row>k)
                    {
                        row=k;
                        delta=-1;
                    }
                    ma[row].push(id);
                    row+=delta;
                }
            }
            else//整个蛇形填
            {
                int delta=1,row=1;
                for(int i=1;i<=n;i++)
                {
                    if(row<1)
                    {
                        row=1;
                        delta=1;
                    }
                    else if(row>k)
                    {
                        row=k;
                        delta=-1;
                    }
                    ma[row].push(i);
                    row+=delta;
                }
            }
            for(int i=1;i<=k;i++)
            {
                while(!ma[i].empty())
                {
                    printf("%d ",ma[i].front());//还好不卡行末空格
                    ma[i].pop();
                }
                puts("");
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    SQL Server 中,将多行转换为一行,用某个符号隔开的SQL 语句
    JQ 轻松学会$.get(),$.post(),$ajax()的作用和用法
    Dynamics 365 组织服务查询时,关于输入时间和输出时间的时区问题讲解
    在Dynamics 365的标准窗体,lookup字段变成了[Object Object],picklist直接展示数字
    C# 回顾正则表达式的常用语法
    观洛马琴科对决洛佩兹比赛有感
    读取硬盘序列号
    delphi 数组复制利用CopyMemory 最为完美
    MFC多线程通讯--自定义消息
    MFC 多线程及线程同步
  • 原文地址:https://www.cnblogs.com/wawcac-blog/p/11303310.html
Copyright © 2011-2022 走看看