【数论 dp】2048
考场上一个DFS优化乱加就对了一个无解的点
题目描述
给定一个长度为 n 的数列,在这个数列中选取一个子序列使得这个子序列中的数能合出2048
对于合并操作,可以选择这个序列中的任意两个数进行合并,当然这两个数必须是相同的(即2个x合并后成为一个2x)
对于每个序列,只要进行若干次合并操作后,这个序列中至少有一个2048(可以有其他数剩余),就称这个序列是合法的
我们可以认为只要选取的数在原数列中的位置不同,这些序列就是不同的
对于给定的数列,小朋友们需要算出有多少子序列是合法的,并把这个数 对 998244353 取模
输入格式
第一行一个正整数n表示数列长度。
第二行 n 个数 Ai,表示这个数列。
输出格式
输出一行,为序列数模 998244353 后的值。
一些说明与提示
与原版2048不同,这个数列中的数可以是2048以内的任意正整数。
998244353=119×2^23+1,与本题的解题无关。
数据规模与约定
40%的数据,n≤20
70%的数据, n≤500
100%的数据, n≤100000,1≤Ai≤2048
时间限制:1s
空间限制:256MB
题目分析
首先在题意里显然的是,只有2的幂次数能够最终合并出2048。
那么自然先考虑纯粹的dfs。
裸的DFS
枚举出一种方案之后只需要将所有是2的幂的数加起来,如果合法则和必定大于2048(有些单个无法合并的并不造成影响,因为它们既然合并不了(个数为奇数个)那么加上去就不会大于2048)
优化的DFS
1.把a[]从大到小排序,dfs时候先枚举选当前数
2.dfs时如果check()已经合法,那么显然的,后面的x个数无论怎么选都一定符合条件。
可以发现存在这个恒等式,于是直接加上即可(当然要快速幂或者事先打好一个表。要知道直接右移100000位会炸到哪里去都不知道)
3.测试数据会发现,在最后的几个数(通常为0或1)dfs时,会因sum为2046/2047这样的东西被卡很久。那么我们维护对2的幂次维护一个后缀和,加判断 if sum+suffix[now]< return 意即如果后面全部数选上,都还达不到2048那就一定不合法了。
#include<bits/stdc++.h>
using namespace std;
int n,nx,a[],tr,tot;
long long ans,suf[],m2[],ss;
bool vis[];
set<int>f;
const int MO = ;
int read()
{
char ch = getchar();int num = ;
while (ch<''||ch>'')ch = getchar();
while (ch>=''&&ch<=''){num=num*+ch-'';ch=getchar();}
return num;
}
bool cmp(int a, int b)
{
return a>b;
}
bool check() //原始的check
{
tot = ;
int ax[],x,y,z;
memset(ax, , sizeof(ax));
register int i;
//a1:1 a2:2 a3:4 a4:8 a5:16 a6:32 a7:64 a8:128 a9:256 a10:512 a11:1024
for (i=; i<=n; i++)
if (vis[i])
{
x = a[i];
if (x==)return ;
if (x==||x==||x==||x==||x==||x==||x==||x==||x==||x==||x==){
y = x;z = ;
while (y!=){y>>=;++z;}
ax[z]++;
}
}
for (i=; i<=; i++)
ax[i+] += ax[i]/;
if (ax[])return ;
return ;
}
bool new_check() //略有升级的check
{
int cnt = ;
for (int i=; i<=n; i++)
if (vis[i] && f.count(a[i]))
cnt+=a[i];
return cnt>=;
}
void search(int now, int sum) //check 是O(n)的,浪费很多。直接保存sum每次传递
{
if (now==n+)
{if (sum>=){ans++;if (ans>=MO)ans-=MO;}return;}
if (sum>=){
ans += m2[n-now+];
if (ans>=MO)ans-=MO;
return;
}
if (sum+suf[now]<)return;
vis[now] = ;
if (f.count(a[now]))
search(now+, sum+a[now]);
else search(now+, sum);
vis[now] = ;
search(now+, sum);
}
int main()
{
register int i,j;
for (int i=; i<=; i++)
f.insert(<<i);
memset(vis, , sizeof(vis));
n = read();nx = ;
for (i=; i<=n; i++)
{
tr = read();
a[++nx] = tr;
}
n = nx;
sort(a+, a+n+, cmp);
m2[] = ;
for (int i=; i<=n; i++)
m2[i] = m2[i-]*%MO;
for (i=n; i>=; i--)
if (f.count(a[i]))
suf[i] = suf[i+]+a[i];
search(, );
printf("%lld\n",ans);
return ;
}
嗯这样优化之后就可以过5个点(不过还有四个点WA,最后的点TLE?这不是幂数模数的问题,玄学)
假的DFS优化
像我这样在读入的时候只读2的幂次数的人已经不多了
数学考虑
既然我们都已经在dfs优化中想到:如何计算一个集合元素为n的所有子集个数。
我们再往dp靠一靠就可以得到一个比标算的代码复杂度还要少的算法了。先贴上:
#include<bits/stdc++.h>
using namespace std;
const int maxx = ;
const int MO = ;
int read()
{
char ch = getchar();int num;
for (num = ; !isdigit(ch); ch = getchar());
for (; isdigit(ch); ch = getchar())num = (num<<)+(num<<)+ch-;
return num;
}
long long qmi(long long a, long long b)//快速幂
{
long long ret = , xx = a;
for(;;)
{
if (b&)ret=(xx*ret)%MO;
if (b>>=)xx=(xx*xx)%MO;
else break;
}
return ret;
}
int n,m,x;
long long f[],ss;
int main()
{
n = read();
f[] = ;
for (int i=; i<=n; i++)
{
x = read();
if (x & (x-))continue; //判断是否为2的幂次
m++;
for (int j=maxx-; j>=x; j--)
f[j] = (f[j]+f[j-x])%MO;
}
for (int i=; i<maxx; i++)
ss = (f[i]+ss)%MO;
printf("%lld\n",(qmi(, m)-ss+MO)%MO*qmi(, n-m)%MO);
return ;
}
我们先算出有2的幂次数m个
记m个数共可以表示出方案数a
记m个数可表示出小于2048的数的方案为b
显然这m个数可表示出大于等于2048的方案数即a-b,就是上述的 (qmi(, m)-ss+MO)%MO ,至于在模之前加上MO是因为这个qmi()和ss取模过,故不能保证取模后依然qmi()>ss
后面的 *qmi(, n-m)%MO 就是乘上对答案无关的方案数。
至此,我们用41行就跑过了这题……
标算
最后贴一发标算
一个子序列或者说子集合法的条件是:只要二的次幂的和 不小于 2048 即可
可以想象,二进制加法即2048合并的过程
转化为用DP统计有多少子集的和不小于 2048 ,很简单的一个背包问题
大概大佬都是这样简洁的“很简单的xx问题”的吧
不过好像现在做完再看这题也“很简单的数学+dp问题”
我对标算的理解都写在注释里了
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll; inline char nc(){
static char buf[],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,,,stdin); if (p1==p2) return EOF; }
return *p1++;
} inline void read(int &x){ //指针快读
char c=nc(),b=;
for (;!(c>='' && c<='');c=nc()) if (c=='-') b=-;
for (x=;c>='' && c<='';x=x*+c-'',c=nc()); x*=b;
} const int P=; //神奇的模数
const int N=; int n,a[N];
ll f[][],sum[];
int tag[N],cnt[N]; ll fac[N],inv[N]; //fac[]阶乘 inv[]逆元
inline void Pre(){ //预处理阶乘及逆元(inv[])
fac[]=;
for (int i=;i<=n;i++) fac[i]=fac[i-]*i%P;
inv[]=;
for (int i=;i<=n;i++) inv[i]=(P-P/i)*inv[P%i]%P;
inv[]=;
for (int i=;i<=n;i++) (inv[i]*=inv[i-])%=P;
} inline ll Pow(ll a,int b){ //快速幂
ll ret=;
for (;b;b>>=,a=a*a%P) if (b&) ret=ret*a%P;
return ret;
} #define C(n,m) (fac[(n)]*inv[(m)]%P*inv[(n)-(m)]%P) //跑组合数 int main(){
read(n); Pre();
for (int i=;i<=n;i++) read(a[i]);
for (int i=;i<=;i<<=) tag[i]=; //2的幂次
int tem=;
for (int i=;i<=n;i++)
tag[a[i]]?cnt[a[i]]++:(tem*=)%=P; //cnt[] 2的幂次的数的个数
f[][]=; sum[]=;
int t=;
for (int i=;i<=;i<<=,t++){ //背包转移
ll tsum=;
for (int k=;i*k<= && k<=cnt[i];k++){
int tmp=i*k; // n
(tsum+=C(cnt[i],k))%=P; //实际上std似乎没有意识到ΣC(i, n)=2^n?
// i=0
for (int j=;~j;j--) (f[t][min(tmp+j,)]+=f[t-][j]*C(cnt[i],k)%P)%=P;
}
(f[t][]+=(Pow(,cnt[i])-tsum+P)*sum[t-]%P)%=P;
for (int j=;j<=;j++) (sum[t]+=f[t][j])%=P;
}
printf("%lld\n",f[t-][]*tem%P);
return ;
}
END
【数论 dp】2048的更多相关文章
- 【bzoj1408】[Noi2002]Robot 数论+dp
题目描述 输入 输出 样例输入 3 2 1 3 2 5 1 样例输出 8 6 75 题解 语文题+数论+dp 花了大段讲述什么叫mu,什么叫phi,只是新定义的mu将2看作有平方因子,新定义的phi( ...
- 洛谷$P5366\ [SNOI2017]$遗失的答案 数论+$dp$
正解:数论$dp$ 解题报告: 传送门$QwQ$ 考虑先质因数分解.所以$G$就相当于所有系数取$min$,$L$就相当于所有系数取$max$ 这时候考虑,因为数据范围是$1e8$,$1e8$内最多有 ...
- 数论+DP HDOJ 4345 Permutation
题目传送门 题意:一个置换群,经过最少k次置换后还原.问给一个N个元素,在所有的置换群里,有多少个不同的k. 分析:这道题可以转化成:N = Σ ai ,求LCM ( ai )有多少个不同的值.比如N ...
- HDU 5656 CA Loves GCD (数论DP)
CA Loves GCD 题目链接: http://acm.hust.edu.cn/vjudge/contest/123316#problem/B Description CA is a fine c ...
- Codeforces 264B 数论+DP
题目链接:http://codeforces.com/problemset/problem/264/B 代码: #include<cstdio> #include<iostream& ...
- 数论+dp Codeforces Beta Round #2 B
http://codeforces.com/contest/2/problem/B 题目大意:给你一个n*n的矩形,问从(1,1)出发到(n,n),把图中经过的所有的数字都乘在一起,最后这个数字有多少 ...
- C. Multiplicity 简单数论+dp(dp[i][j]=dp[i-1][j-1]+dp[i-1][j] 前面序列要满足才能构成后面序列)+sort
题意:给出n 个数 的序列 问 从n个数删去任意个数 删去的数后的序列b1 b2 b3 ......bk k|bk 思路: 这种题目都有一个特性 就是取到bk 的时候 需要前面有个bk-1的序列前 ...
- Codeforces1097D. Makoto and a Blackboard(数论+dp+概率期望)
题目链接:传送门 题目大意: 给出一个整数n写在黑板上,每次操作会将黑板上的数(初始值为n)等概率随机替换成它的因子. 问k次操作之后,留在黑板上的数的期望. 要求结果对109+7取模,若结果不是整数 ...
- SCOI2009游戏 (数论+dp)
题解 很显然,对于一个确定的排列,每个数字的移动规则是一定的,我们根据这个排列,把它抽象为i向a[i]连一条边,很显然最后会构成一个环,那么行数就是这些环长的lcm. 那么问题变成了把n任意进行划分, ...
随机推荐
- Spring security + oauth2.0 + redis + mybatis plus 搭建微服务
上个星期一个朋友请求帮忙,让我搭建一个分布式授权中心的微服务,之前我也没搭建过,在网上撸了几天前辈们写的技术博客,搞出个模型,分享给大家: 前辈们博客地址: OAuth2.0 原理:https://b ...
- shell学习(3)- grep
常用选项 -E :开启扩展(Extend)的正则表达式. -i :忽略大小写(ignore case). -v :反过来(invert), 显示不包含匹配文本的所有行. -V 或 --vers ...
- [题解](树的计数)luogu_P4430猴子打架_/_luogu_P4981父子
来源:题解 比较不错的博客:http://www.cnblogs.com/dirge/p/5503289.html 最后生成一颗无根树,有n^(n-2)种情况,打架的顺序有(n-1)!种 #inclu ...
- bzoj2740 串 && bzoj2176 strange string(最小表示法模板)
https://konnyakuxzy.github.io/BZPRO/JudgeOnline/2740.html 题解讲的很清楚了 (好像等于的情况应该归入case2而不是case1?并不确定) 具 ...
- java中代码执行顺序
静态代码块 -- >构造代码块 --> 构造方法静态代码块:只执行一次构造代码块:每次调用构造方法都执行 http://blog.csdn.net/wuhaiwei002/article/ ...
- 開玩樹莓派(二):配置IP,實現無顯示器局域網內Putty連接和RDP遠程
目錄: 開玩樹莓派(一):安裝Raspbian系統 開玩樹莓派(二):配置IP,實現無顯示器局域網內Putty連接和RDP遠程 開玩樹莓派(三):Python編程 開玩樹莓派(四):GPIO控制和遠程 ...
- VS局域网断点调试设置
1.电脑文档文件夹下\IISExpress\config文件内找到applicationhost.config文件编辑 找到<sites>节点 找到你要编辑的site节点 在<bin ...
- 一行JS搞定快速关机
一.在本地新建一个文件js文件 JS代码: (new ActiveXObject("Shell.Application")).ShutdownWindows(); 二.设置快捷键 ...
- 在PaaS上开发Web、移动应用(2)
在PaaS上开发Web.移动应用(2) PaaS学习笔记目录 PaaS基础学习(1) 在PaaS上开发Web.移动应用(2) PaaS优点与限制(3) 6. 巨型代码,是指持续不断地向一个应用程序添加 ...
- IOS画线条
- (void)drawRect:(CGRect)rect { // draw a rounded rect bezier path filled with blue CGContextRef aRe ...