51Nod 1383 整数分解为2的幂
51Nod 3212 数字变位
51Nod 2128 前缀异或
LightOJ 1341 Aladdin and the Flying Carpet
POJ 3061 Subsequence
Codeforces 1263A Sweet Problem
51Nod 1383 整数分解为2的幂
题目链接:点击这里
1 = 1
2 = 1 + 1
2 = 2
3 = 1 + 1 + 1
3 = 1 + 2
4 = 1 + 1 + 1 + 1
4 = 1 + 1 + 2
4 = 2 + 2
4 = 4
5 = 1 + 1 + 1 + 1 + 1
5 = 1 + 1 + 1 + 2
5 = 1 + 2 + 2
5 = 1 + 4
6 = 1 + 1 + 1 + 1 + 1 + 1
6 = 1 + 1 + 1 + 1 + 2
6 = 1 + 1 + 2 + 2
6 = 2 + 2 + 2
6 = 1 + 1 + 4
6 = 2 + 4
7 = 1 + 1 + 1 + 1 + 1 + 1 + 1
7 = 1 + 1 + 1 + 1 + 1 + 2
7 = 1 + 1 + 1 + 2 + 2
7 = 1 + 2 + 2 + 2
7 = 1 + 1 + 1 + 4
7 = 1 + 2 + 4
8 = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
8 = 1 + 1 + 1 + 1 + 1 + 1 + 2
8 = 1 + 1 + 1 + 1 + 2 + 2
8 = 1 + 1 + 2 + 2 + 2
8 = 2 + 2 + 2 + 2
8 = 1 + 1 + 1 + 1 + 4
8 = 1 + 1 + 2 + 4
8 = 2 + 2 + 4
8 = 4 + 4
8 = 8
9 = ......
不难发现,当 为奇数时, 的划分数就等于 的划分数,即 所划分的每个等式只是在 所划分的每个等式的基础上多加了个 而已。
当 为偶数时,规律比较难找,我做题时也就卡在这了~
结论:当 为偶数时, 的划分数等于 的划分数加上 的划分数。
解释:以上面 的所有等式为例,
8 = 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
8 = 1 + 1 + 1 + 1 + 1 + 1 + 2
8 = 1 + 1 + 1 + 1 + 2 + 2
8 = 1 + 1 + 2 + 2 + 2
8 = 1 + 1 + 1 + 1 + 4
8 = 1 + 1 + 2 + 4
上面这部分等于 的划分数,除去此部分,剩下的等式都是由 的幂组成,于是全都除以 ,可以发现,剩下的部分就等于 的划分数。
8 = 2 + 2 + 2 + 2
8 = 2 + 2 + 4
8 = 4 + 4
8 = 8
4 = 1 + 1 + 1 + 1
4 = 1 + 1 + 2
4 = 2 + 2
4 = 4
AC代码:
#include
#include
#include
using namespace std;
const int mod = 1000000007;
const int N = 1e6 + 10;
int f[N];
int main()
{
int n;
scanf("%d", &n);
f[1] = 1;
for(int i = 2; i <= n; i++)
{
if(i & 1) f[i] = f[i - 1];
else f[i] = (f[i - 1] + f[i >> 1]) % mod;
}
printf("%d", f[n]);
return 0;
}
51Nod 3212 数字变位
题目链接:点击这里
#include
#include
#include
using namespace std;
const int N = 110;
int a[N];
bool cmp(int a, int b)
{
return a > b;
}
int main()
{
int x;
scanf("%d", &x);
int cnt = 0;
while(x)
{
a[cnt++] = x % 10;
x /= 10;
}
sort(a, a + cnt, cmp); // 从大到小
int maxx = 0;
for(int i = 0; i < cnt; i++) maxx = maxx * 10 + a[i];
printf("%d ", maxx);
sort(a, a + cnt); // 从小到大
int i;
for(i = 0; i < cnt; i++)
if(a[i] != 0)
break;
if(i == cnt) printf("0\n");
else
{
printf("%d", a[i]);
for(int j = 0; j < i; j++) printf("0");
for(int j = i + 1; j < cnt; j++) printf("%d", a[j]);
}
return 0;
}
51Nod 2128 前缀异或
题目链接:点击这里
#include
#include
#include
using namespace std;
const int N = 100010;
int a[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i] = a[i - 1] ^ a[i];
}
int m, l, r;
scanf("%d", &m);
while(m--)
{
scanf("%d%d", &l, &r);
printf("%d\n", a[r] ^ a[l - 1]);
}
return 0;
}
LightOJ 1341 Aladdin and the Flying Carpet
题目链接:点击这里
题意:现在已知地毯的面积和地毯可能的最小边的长度,您的任务是找出可能有多少种地毯。例如,地毯的面积为12,地毯的最小可能边是2,那么就可以有两种地毯,它们的边是 {2,6} 和 {3,4}。
思路:首先求出 的正约数的个数,然后再除以 得到 的正约数的对数,最后再枚举出 中 的正约数的个数,相减便是答案。
注意:如果每组样例都用 的时间求解出 的正约数个数,会超时。所以,先预处理出 以内的所有素数,然后在每次求解正约数个数时,直接利用已经筛好的素数即可。
AC代码:
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 1500000;
int p[N + 10], cnt; // p[]存储所有素数
bool st[N + 10]; // st[x]存储x是否被筛掉
void get_primes() // 线性筛
{
for(int i = 2; i <= N; i++)
{
if(!st[i]) p[cnt++] = i;
for(int j = 0; p[j] <= N / i; j++)
{
st[p[j] * i] = true;
if(i % p[j] == 0) break;
}
}
}
int get_divisors(ll n) // 求 n 的约数个数
{
int res = 1;
for(int i = 0; p[i] <= n / p[i]; i++)
{
int c = 0;
while(n % p[i] == 0)
{
c++;
n /= p[i];
}
res *= c + 1;
}
if(n > 1) res *= 1 + 1;
return res;
}
int main()
{
get_primes();
int T;
scanf("%d", &T);
for(int k = 1; k <= T; k++)
{
ll a, b;
scanf("%lld%lld", &a, &b);
if(b * b >= a)
{
printf("Case %d: 0\n", k);
continue;
}
int ans = get_divisors(a) / 2;
for(int i = 1; i < b; i++)
if(a % i == 0)
ans--;
printf("Case %d: %d\n", k, ans);
}
return 0;
}
POJ 3061 Subsequence
题目链接:点击这里
题意:给定长度为 的数列整数 以及整数 。求出总和不小于 的连续子序列的长度的最小值。如果解不存在,则输出 。
前缀和+二分
由于所有的元素都大于零,如果子序列 满足 ,那么,对于任何的 一定有 。
因此,首先预处理出前缀和 ,然后枚举起点 ,对于每一个起点 ,在其后面二分找到第一个满足 的终点 ,统计出所有区间长度 的最小值即可。
时间复杂度是
#include
#include
#include
#include
using namespace std;
const int N = 100010;
int s[N];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int n, target, x;
scanf("%d%d", &n, &target);
for(int i = 1; i <= n; i++)
{
scanf("%d", &x);
s[i] = s[i - 1] + x;
}
if(s[n] < target) // 解不存在
{
puts("0");
continue;
}
int res = n;
for(int start = 1; s[start - 1] + target <= s[n]; ++start) //枚举区间起点
{
int end = lower_bound(s + start, s + n + 1, s[start - 1] + target) - s; //二分终点
res = min(res, end - start + 1);
}
printf("%d\n", res);
}
return 0;
}
尺取法
我们设以 开始、总和最初大于 时的连续子序列为 ,从 开始总和最初超过 的连续子序列如果是 的话,则必然有 。
利用这一性质便可以设计出如下算法:
- 以 初始化。
- 只要依然有 ,就不断将 增加 ,并将 增加 。
- 如果(2)中无法满足 则终止。否则的话,更新 。
- 将 减去 ,增加 然后回到(2)。
对于这个算法,因为 最多变化 次,因此只需 的复杂度就可以求解这个问题了。
#include
#include
#include
#include
using namespace std;
const int N = 100010;
int a[N];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int n, target;
scanf("%d%d", &n, &target);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
int res = 1e9;
int s = 0, t = 0, sum = 0;
while(true)
{
while(t < n && sum < target) sum += a[t++];
if(sum < target) break;
res = min(res, t - s);
sum -= a[s++];
}
if(res > n) res = 0; //解不存在
printf("%d\n", res);
}
return 0;
}
Codeforces 1263A Sweet Problem
题目链接:点击这里
#include
#include
#include
using namespace std;
int a[5];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
for(int i = 0; i < 3; i++) scanf("%d", &a[i]);
sort(a, a + 3);
int ans = a[0]; // 先吃完最小堆a[0]
if(a[2] - a[0] >= a[1]) // 若最大堆a[2]足够抵消最小堆a[0]
{
ans += a[1];
}
else // 若最大堆a[2]不足以抵消最小堆a[0],那就把a[2]吃到和a[1]相等
{
int t = a[0] - (a[2] - a[1]);
if(t & 1) ans += a[1] - t / 2 - 1;
else ans += a[1] - t / 2;
}
printf("%d\n", ans);
}
return 0;
}