一道凑数问题

Problem Description

题目来源:http://blog.youkuaiyun.com/ljhandlwt/article/details/75014415
给定n,给定池,求池的子集的和为n的个数。池为2^0,2^0,2^1,2^1…包含任意2的次幂,且每个数都有两份。

Solution

对于任意2^i,有2^i=2^i=2^(i-1)*2=sum(2^(i-j))+2^(i-j-1)*2
其形式为连续的2^次幂累加及末尾最低项乘2。

题目表示任意2的次幂有两份。
因此若n=2^N+2^M,且N<M,
则我们可以认为,2^M只能来自于2^N,2^N+1,.....2^M。
(更低位中一定存在一位已被2^N使用了两次。特殊的,若2^N只使用了自己,2^M可以使用更低位,但其方案与2^N使用更低位是重复的)

将n按二进制分解,有n=2^a1+2^a2+....2^am, a1<a2<...<am

则可以dpf[i]表示凑到2^ai时的方案数。
对于凑2^a[i],方案总共a[i]-a[i-1]+1种。
这里的一个问题是2^a[i-1]可能在f[i-1]用到过,所以需要容斥一下。
2^a[i-1]是凑2^a[i-1]的方案一种,其对于f[i-1]的总贡献就是f[i-2]。

此时,对于f[i],占用2^a[i-1]的那一种方案不可用,其他可用。
同理,f[i-1]的剩余部分可以使用f[i]的所有方案。
所以有:
f[i]=(f[i-1]-f[i-2])*(a[i]-a[i-1]+1)+f[i-2]*(a[i]-a[i-1])
即
f[i]=f[i-1]*(a[i]-a[i-1]+1)-f[i-2]

PS:对于本题,用f[i][0/1]表示凑到第i个数时,用或者不用自己来凑,这样的DP方式可能更自然。因为笔者智商极低,使用这种二维DP做完后才发现可以化简成一维,而后强行思考,才得出了这种容斥思路。

Code

#include <cstdio>
using namespace std;
int a[66],n,f[66];
int main()
{
    int N;
    while (scanf("%d",&N)!=EOF)
    {
        int i=0; n=0;
        while (N)
        {
            if (N%2) a[++n]=i;
            N/=2; i++;  
        }
        f[0]=1; f[1]=a[1]+1;
        for (i=2;i<=n;i++)  f[i]=f[i-1]*(a[i]-a[i-1]+1)-f[i-2];
        printf("%d\n",f[n]);
    }
}
在 C++ 中,“凑数”并不是一个标准术语,通常不会出现在官方文档或技术资料中。然而,在某些特定场景下,“凑数”的概念可能被用来描述一种编程技巧或者设计模式,其目的是为了满足某种约束条件而填充数据。 以下是几种可能的情况以及对应的示例: --- ### 1. 使用占位符填补数组大小不足 当定义固定大小的数组时,如果实际的数据量不足以填满整个数组,则可以通过“凑数”的方式补充剩余部分。这种做法常见于初始化阶段。 ```cpp #include <iostream> using namespace std; int main() { const int size = 5; int data[] = {1, 2}; // 实际只有两个有效数据 // “凑数”:用默认值补全剩下的位置 for (int i = sizeof(data)/sizeof(data[0]); i < size; ++i) { data[i] = 0; // 假设用0作为填充值 } cout << "Array after padding:" << endl; for (int i = 0; i < size; ++i) { cout << data[i] << " "; } return 0; } ``` 上述代码通过循环将未赋初值的部分设置为 `0`,从而实现“凑数”。 --- ### 2. 构造函数中的参数补齐 有时类的设计需要支持多种构造形式,但在调用时可能会缺少一些可选参数。此时可以在内部提供默认值来完成这些缺失项。 #### 示例: ```cpp class Point { public: double x, y, z; // 提供带默认值的构造函数 Point(double _x = 0, double _y = 0, double _z = 0) : x(_x), y(_y), z(_z) {} }; int main() { Point p1(3); // 只传入了一个坐标,默认其他为零 Point p2(4, 5); // 传递了两个坐标,默认第三个为零 Point p3; // 不传任何参数,默认全部为零 cout << "(" << p1.x << ", " << p1.y << ", " << p1.z << ")" << endl; cout << "(" << p2.x << ", " << p2.y << ", " << p2.z << ")" << endl; cout << "(" << p3.x << ", " << p3.y << ", " << p3.z << ")" << endl; return 0; } ``` 这里利用了构造函数重载和默认参数机制实现了自动“凑数”。[^1] --- ### 3. 数据结构对齐时的填充字节 在低级内存管理中,编译器会根据目标平台的要求调整字段排列顺序并插入额外的空间(即所谓的“padding bytes”),以便优化访问效率。“凑数”在这里表现为人为控制这类行为。 #### 示例: ```cpp #pragma pack(push, 1) // 禁用默认对齐规则 struct MyStruct { char c; short s; }; #pragma pack(pop) int main(){ cout << "Size of struct with manual alignment: " << sizeof(MyStruct) << endl; // 如果不指定pack属性,size可能是更大的数值因为存在隐式的pad byte return 0; } ``` 此片段展示了如何强制关闭自然边界对准以减少不必要的空间浪费——这也是一种广义上的“凑数”操作。[^2] --- ### 4. 随机生成唯一标识符作为填充物 对于某些应用而言,随机产生的 UUID/GUID 能够充当临时性的“凑数”角色。例如在网络通信协议里发送消息之前先附加一个独一无二的消息ID便于后续追踪处理状态变化情况等等... #### 示例基于Boost库创建UUID: ```cpp #include <boost/uuid/uuid.hpp> /* class/headers */ #include <boost/uuid/uuid_generators.hpp>/* generators */ #include <boost/uuid/uuid_io.hpp> /* streaming operators etc. */ namespace buuids=boost::uuids; buids::uuid GenerateUniqueId(){ static auto gen=buids::random_generator(); return gen(); } void ExampleUsage(){ buids::uuid id=GenerateUniqueId(); std::cout<<id<<"\n"; } ``` 该方法适用于那些需要动态分配资源而又不想暴露真实编号的应用场合。[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值