zoukankan      html  css  js  c++  java
  • XJOI 夏令营501-511NOIP训练18 高二学堂

    在美丽的中山纪念中学中,有座高二学堂,同样也是因为一个人,让它们变

    成了现在这个样子~那就是我们伟大的级主任。
    因为他,我们又迎来了一个木有电影,只有对答案的段考日;又迎来了一个
    不是大礼拜,而是小礼拜的周末。因为是小礼拜,同学们都不回家,所以干脆就
    回到宿舍去玩牌了。而由于三国杀太out 了,所以现在他们都玩四国杀。
    四国杀(说白了就是扑克牌~)是Wayne 发明的,源于他对升级、斗地主、锄
    大地等等玩法都感到厌倦了。于是他提出了这个新的玩法:
    Wayne 有一副加强版的扑克牌,强大到任意取一个自然数x,在牌堆里都恰
    有4 张数值为x 的牌。每次,Wayne 随机生成两个正整数n 和k,然后在牌堆
    里选取不超过k 张牌,使得牌面数字之和恰为n。已知Wayne 玩了若干盘,每
    盘都算出了对应的方案数,他想请你也算出各盘方案数,以验算他的结果是否正
    确。
    结果可能比较大,你只需要求出方案数mod 1000000009 的值即可。

    输入格式:

    输入文件包含不超过10 组数据。
    每行包含两个整数,表示上文中的n 和k。
    输入数据以两个0 表示结束。

    输出格式:

    输出文件中,每组数据输出一行,为对应的方案数。

    样例输入:

    2 1
    2 2
    2 3
    50 5
    0 0

    样例输出:

    4
    10
    10
    1823966

    数据范围:

    对于10%的数据,k=1;
    对于20%的数据,n≤10,k≤4;
    对于40%的数据,n≤1000;
    对于60%的数据,n≤100000;
    对于另外20%的数据,只有1 组数据;
    对于100%的数据,n≤10^9,k≤10。

    时间限制:

    1S

    空间限制:

    256M

    DP+矩阵快速幂

    神仙题

    在考场上我只想出来了40分做法

    40分:

    可以发现这可以看做一个二维的01背包

    一维是选的牌数字总和大小,还有一维是所选牌的个数

    相同牌面的牌有4张,那么将这种牌拆成4个物品

    设$dp[i][j][k]$表示前$i$件物品,总和为$j$,一共有$k$张牌的方案数

    可以用滚动数组将第一维滚动掉

    #include <bits/stdc++.h>
    #pragma GCC optimize(2)
    #define mod 1000000009
    using namespace std;
    int n,k,dp[11000][12];
    int main()
    {
        while (1)
        {
            scanf("%d%d",&n,&k);
            if (n==0 && k==0)
              break;
            if (n==0 || k==0)
            {
                printf("0
    ");
                continue;
            }
            dp[0][0]=1;
            for (int i=1;i<=4*n;i++)
            {
                int v;
                v=(i-1)/4+1;//算出当前的牌面数字
                for (int j=n;j>=v;j--)
                {
                    for (int p=min(i,k);p>=1;p--)
                    {
                        dp[j][p]=(dp[j][p]+dp[j-v][p-1])%mod;//同背包转移
                    }
                }
            }
            int ans=0;
            for (int i=0;i<=k;i++)
              ans=(ans+dp[n][i])%mod;//统计答案
            printf("%d
    ",ans);
            for (int i=0;i<=n;i++)
            {
                for  (int j=0;j<=k;j++)
                  dp[i][j]=0;
            }
        }
    }

    80分:

    可以发现一个性质

    对于已选的牌的集合中只有两种提高总和的方法

    注意此处不再是一张一张选

    1.全体数+1

    2.选若干张牌面为1的牌(数量小于等于4)

    那么所有选牌的方法都可以由这种方法得到

    如1操作,相当于将之前所选的牌都放回去,重新拿走牌面比原来多1的牌

    那么牌面为1的牌不会出现在现在所选的牌中,那么又有4张新的1可以选择

    那么进行2操作

    那么记$dp[i][j]$表示当前总和为$i$,已经选了$j$张牌的方案数

    可以将2个操作合并成一个操作

    那么转移方程为

    $dp[i][j]=dp[i][j]+dp[i-j][i-k]*c[k]$

    其中$c[k]$表示从4张牌找出k张牌的方案数

    第一维的变化表示1操作,第二维的变化表示2操作

    #include <bits/stdc++.h>
    #define mod 1000000009
    #define ll long long
    using namespace std;
    ll n,k,dp[200100][11];
    ll c[5];int main()
    {
        c[0]=1;
        c[1]=4;
        c[2]=6;
        c[3]=4;
        c[4]=1;
        while (1)
        {
            scanf("%lld%lld",&n,&k);
            if (n==0 && k==0)
              break;
            memset(dp,0,sizeof(dp));
            dp[0][0]=1;
            for (ll i=1;i<=n;i++)
            {
                for (ll j=1;j<=min(i,k);j++)
                {
                    for (ll p=0;p<=min((ll)4,j);p++)
                    {
                        if (i>=j)//注意边界
                          dp[i][j]=(dp[i][j]+dp[i-j][j-p]*c[p])%mod;
                    }
                }
            }
            ll ans=0;
            for (ll i=0;i<=k;i++)
              ans=(ans+dp[n][i])%mod;
            printf("%lld
    ",ans);
        }
    }

    100分:

    其实这种DP的转移可以用矩阵优化

    因为$dp[i][j]$只会由前$k$层$DP$转移过来,那么用一个矩阵记录第$i$层到第$i-k$层$dp$的值

    $egin{bmatrix} dp[i][0] & dp[i][1] & ... & dp[i][k] & ... & dp[i-k][0] & dp[i-k][1] & ... & dp[i-k][k] end{bmatrix}$

    那么现在只要在构造一个转移矩阵,那么就可以转移DP了

    那么将dp转移展开

    $dp[i][j]=dp[i-j][j]*c[0]+dp[i-j][j-1]*c[1]+dp[i-j][j-2]*c[2]+dp[i-j][j-3]*c[3]+dp[i-j][j-4]*c[4]$

    那对于每一个$dp[i][j]$找出初始矩阵的位置,并在转移矩阵中填入相应位置即可

    注意这里只要将$dp[i][j]$转移到$dp[i+1][j]$,$dp[i-1]$到$dp[i-k]$的值都己经有,可以直接平移到下一行

    在转移矩阵中斜着填入1即可

    此处给出7 2的转移矩阵

    $egin{matrix} 0 & 4 & 0 & 1 & 0 & 0 & 0 & 0 & 0\ 0 & 1 & 0 & 0 & 1 & 0 & 0 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\ 0 & 0 & 6 & 0 & 0 & 0 & 1 & 0 & 0\ 0 & 0 & 4 & 0 & 0 & 0 & 0 & 1 & 0\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 1\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ end{matrix}$

    #include <bits/stdc++.h>
    #define mod 1000000009
    #define ll long long
    using namespace std;
    ll n,k,dp[35][11];
    ll c[5];
    struct node
    {
        ll n,num[130][130];
        void init()
        {
            for (int i=1;i<=n;i++)
            {
                for (int j=1;j<=n;j++)
                  num[i][j]=0;
            }//初始化
        }
    }tr,f;
    node operator * (node a,node b)
    {
        node c;
        c.n=a.n;
        c.init();
        for (int i=1;i<=a.n;i++)
        {
            for (int j=1;j<=a.n;j++)
            {
                for (int p=1;p<=a.n;p++)
                {
                    c.num[i][j]=(c.num[i][j]+a.num[i][p]*b.num[p][j])%mod;
                }
            }
        }
        return c;
    }//矩阵乘法
    node m_pow(node a,int b)
    {
        node ans;
        ans.n=a.n;
        ans.init();
        for (int i=1;i<=ans.n;i++)
          ans.num[i][i]=1;
        while (b>0)
        {
            if (b&1)
              ans=ans*a;
            a=a*a;
            b>>=1;
        }
        return ans;
    }//矩阵快速幂
    pair <int,int> find(int x)
    {
        pair <int,int> p;
        if (x%(k+1)==0)
        {
            p.first=x/(k+1);
            p.second=k;
        }
        else
        {
            p.first=x/(k+1)+1;
            p.second=x%(k+1)-1;
        }
        return p;
    }//此函数求的是在矩阵中第x个数的dp的下标减去最高层的值
    //也就是矩阵中的坐标
    int wh(pair <int,int> p) { return (k+1)*(p.first-1)+p.second+1; }//逆运算 int main() { c[0]=1; c[1]=4; c[2]=6; c[3]=4; c[4]=1; while (1) { scanf("%lld%lld",&n,&k); if (n==0 && k==0) break; memset(dp,0,sizeof(dp)); dp[0][0]=1; for (int i=1;i<=k;i++) { for (int j=1;j<=min(i,(int)k);j++) { for (int p=0;p<=min(4,j);p++) { if (i>=j) dp[i][j]=(dp[i][j]+dp[i-j][j-p]*c[p])%mod; } } } if (k>=n) { ll ans=0; for (int i=0;i<=k;i++) ans=(ans+dp[n][i])%mod; printf("%lld ",ans); continue; } f.n=tr.n=(k+1)*(k+1); f.init();tr.init(); int x,y; x=k; y=0; for (int j=1;j<=f.n;j++) { f.num[1][j]=dp[x][y]; y++; if (y==k+1) { x--; y=0; } } for (int j=1;j<=k+1;j++) { if (j==f.n) j=f.n; pair <int,int> now; now=find(j); now.first=now.first+now.second-1; for (int p=0;p<=4;p++) { if (now.first>k+1 || now.first<=0 || now.second>k || now.second<0) continue; tr.num[wh(now)][j]=c[p];//构造转移矩阵 now.second=now.second-1; } } for (int j=k+2,nx=1;j<=f.n;j++,nx++) tr.num[nx][j]=1;//将第一层之后的dp值向后平移一位 node final; final=f*m_pow(tr,n-k); ll ans=0; for (int i=1;i<=k+1;i++) ans=(ans+final.num[1][i])%mod;//统计答案 printf("%lld ",ans); } }
  • 相关阅读:
    周总结14
    周总结13
    周总结12
    周总结11
    周总结10
    Pytorch实现GCN、GraphSAGE、GAT
    pytorch在损失函数中为权重添加L1正则化
    conda安装虚拟环境或者软件包时一直报错
    各种报错
    Pytorch-torchtext的使用
  • 原文地址:https://www.cnblogs.com/huangchenyan/p/11298503.html
Copyright © 2011-2022 走看看