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\n");
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\n",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\n",ans);
}
}
100分:
其实这种DP的转移可以用矩阵优化
因为$dp[i][j]$只会由前$k$层$DP$转移过来,那么用一个矩阵记录第$i$层到第$i-k$层$dp$的值
$\begin{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的转移矩阵
$\begin{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\n",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\n",ans);
}
}
XJOI 夏令营501-511NOIP训练18 高二学堂的更多相关文章
- 夏令营501-511NOIP训练18——高二学堂
传送门:QAQQAQ 题意:给你一个数$n$,把它拆分成至多$k$个正整数,使得这些数的和等于$n$且每一个正整数的个数不能超过$4$ 拆分的顺序是无序的,但取出每一个数方案是不同的(例如我要拆$1$ ...
- XJOI夏令营501训练1——分配工作
传送门:QAQQAQ 题意:某公司有工作人员x1,x2,…,xn ,他们去做工作y1,y2,…,ym(n<=m) ,每个人都能做其中的几项工作,并且对每一项工作都有一个固定的效率.问能否找到一种 ...
- test20190802 夏令营NOIP训练18
今天的题很有难度啊.然而我10:40才看题-- 高一学堂 在美丽的中山纪念中学里面,有一座高一学堂.所谓山不在高,有仙则名:水不在深,有龙则灵.高一学堂,因为有了yxr,就成了现在这个样子 = =. ...
- XJOI 夏令营501-511NOIP训练18 高三楼
参观完各种饭堂,学校还有什么著名的景点呢?当然是教室了,此时此刻我 们来到了高三楼.你会发现高三楼门口会有以身份认证系统,这东西还有着一段疼人的历史.每年的九月到来,高三的童鞋大多不习惯学校的作息时间 ...
- 夏令营501-511NOIP训练18——高三楼
传送门:QAQQAQ 题意:定义矩阵A与矩阵B重复,当且仅当A可以通过任意次行列交换得到B,例如下图A,B即为合法矩阵 现求对于$n*n$的矩阵有多少个不重复的矩阵 数据范围: 对于10%的数据 N≤ ...
- XJOI夏令营501-511NOIP训练14——好朋友
传送门:QAQQAQ 题意:noip2011就要来了,W校的同学们不仅看重这次比赛,更看重noip2011和谁住在同一个房间.同学之间的关系好坏可以用一个亲密值表示,亲密值越大,两个同学关系越好.小A ...
- XJOI 夏令营501-511NOIP训练17 蛇形数阵
话说小X在孩提时,都会做标准的蛇形矩阵了,发现很好玩.现在的小X很想对其进行改版,变为如下类型的一个无限大蛇形数阵:令S(x)表示以1为左上角,x为右下角的矩形内所有数之和.例如S(12)就是具有深色 ...
- XJOI 夏令营501-511NOIP训练14 砍树(2)
小A是小B家的园丁.小B的家里有n棵树,第i棵树的横坐标为i.一天,小B交给小A一个任务,让他降低自己家中的某些树木的高度.这个任务对小A来说十分简单,因为他有一把极其锋利的斧头和一门独门砍树秘籍,能 ...
- XJOI 夏令营501-511测试11 游戏
Alice和Bob两个人正在玩一个游戏,游戏有很多种任务,难度为p的任务(p是正整数),有1/(2^p)的概率完成并得到2^(p-1)分,如果完成不了,得0分.一开始每人都是0分,从Alice开始轮流 ...
随机推荐
- ESP8266 玩板记
一.前言 esp8266的玩板记,后面应该会去更一些其他东西,这一块内容,这算是收官之战了. IoT,江湖有缘再相会 二.ESP8266实现WiFi杀手/钓鱼 这次的博客做的是一个娱乐性较强的项目. ...
- unity官方案例精讲(第三章)--星际航行游戏Space Shooter
案例中实现的功能包括: (1)键盘控制飞船的移动: (2)发射子弹射击目标 (3)随机生成大量障碍物 (4)计分 (5)实现游戏对象的生命周期管理 导入的工程包中,包含着一个完整的 _scene--- ...
- 轻松理解JVM的分代模型
前言 上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习. 本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解j ...
- Win10下设置GOPATH系统变量,查看go env没有变化
前言 最近在学习go语言,下载了golang安装包安装之后,使用go env查看环境变量,发现GOPATH已经指向默认的目录了 按照安装手册去系统变量设置了一下GOPATH,指向自定义目录 再来运行一 ...
- 云原生 go-zero 微服务框架
0. go-zero介绍 go-zero是一个集成了各种工程实践的web和rpc框架.通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验. go-zero包含极简的API定义和生成工具goc ...
- (SpringBoot-Jpa)使用Idea数据库自动脚本Generate POJOS生成 Entity对象,
因:使用SpringBoot -jpa,需要手动配置Entity 但是如果你的表中有很多属性,或者有很多表怎么办?? 每个手动写? 还是用mybatis.写mapper??? 解决:使用idea自动工 ...
- 详解command设计模式,解耦操作和回滚
大家好,欢迎来到设计模式专题,我们的主旨是介绍一些有趣好玩的设计模式. 今天我们介绍的设计模式叫做命令模式(command),在这个模式下,我们可以实现do和undo的解耦,让使用方不用关心内部的实现 ...
- C语法-函数不定长参数
目录 前言 语法 va_list va_start va_arg va_end 前言 基于头文件 stdarg.h 基于 STM32 基于 C 如果读者对指针和堆栈的知识点比较熟悉,本笔记就一眼飘过, ...
- 【UR #9】App 管理器
UOJ小清新题表 题目内容 UOJ链接 一句话题意:给出一个强联通的混合图,有一些有向边和无向边.删除一些边使其维持强联通的状态,求删边方案. 数据范围 \(1\leq n\leq 5000,0\le ...
- 【树形DP】ZJOI2008 骑士
题目内容 洛谷链接 有\(n\)位骑士,每个人的战力可能不同,并且每一个人都有且仅有一个憎恨的人,互相憎恨的人不能在同一队中. 求组合为一个骑士队的最大战斗力. PS:可以去看看题目背景学学历史(雾) ...