不降数
题意:
求出恰好
n
n
n 位,从高位到低位非递减这样的数的个数
思路:
可以用矩阵快速幂 或者卡常优化
鉴于牛客跑的快,可以卡常优化过掉
考虑
f
[
i
]
f[i]
f[i] 表示以
i
i
i 结尾的个数
可以发现,递推几次,
f
[
i
]
f[i]
f[i] 就表示几位数,以
i
i
i 结尾的数的个数
舔狗舔到最后一无所有
题意:
思路:
题解
集合划分的思路
牛牛的旅游纪念品
要求选择的任意两个物品之间的距离
>
=
k
>=k
>=k
困难的数学题
要求把
n
n
n 分解乘若干个不小于
k
k
k 的数的方案数
Music Problem
背包dp,抽屉定理,二进制优化,bitset优化
题解
简单瞎搞题
bitset优化
注意这个每次必须转移,所以上一层转移到下层之后,需要清空掉上层的状态
题解
因为要
+
j
∗
j
+j*j
+j∗j,被转移的所有状态都左移
j
∗
j
j*j
j∗j 位,原来为
i
i
i 位,现在成了
i
+
j
∗
j
i+j*j
i+j∗j,或一下就可以将
1
1
1 的状态转移过去,优化掉一层循环
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e6 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
void work()
{
cin >> n;
bitset <maxn> f;
f[0] = 1;
for(int i = 1; i <= n; ++i){
int l, r;cin >> l >> r;
bitset <maxn> tmp;// 空间优化,只和上一层有关
for(int j = l; j <= r; ++j){
tmp |= (f << j * j);// 这样bitset直接或可以优化掉一层for循环,也就是枚举上一层状态的循环
}
f = tmp;
}
cout << f.count();
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
美味菜肴
贪心+dp
失衡天平
题意:
n
n
n 个物品,每个重
w
i
w_i
wi,天平两端的重量之差小于等于
m
m
m 时会平衡,如果平衡则可以那走两边的物品,可以多次称量,求拿走物品重量之和最大是多少
思路:
左右盘差值就是能称出来的重量,所以和砝码称重类似
f
[
i
]
[
j
]
f[i][j]
f[i][j] 前
i
i
i 个物品,左盘和右盘差值为
j
j
j 的最大重量
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int k;
int a[maxn], c[maxn];
int f[109][maxn];
// 前 i 个物品,左盘和右盘差值为 j 的最大重量
void work()
{
cin >> n >> m;
int sum = 0;
for(int i = 1; i <= n; ++i){
cin >> a[i];
sum += a[i];
}
memset(f, -0x3f, sizeof(f));
f[0][0] = 0;
for(int i = 1; i <= n; ++i){
for(int j = 0; j <= sum; ++j){
f[i][j] = f[i-1][j];// 不选a_i
int Max = max(f[i-1][abs(j - a[i])], f[i-1][j + a[i]]);// 选a_i的最大值
f[i][j] = max(f[i][j], Max + a[i]);
}
}
int ans = 0;
for(int i = 0; i <= m; ++i) ans = max(ans, f[n][i]);
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
钉子和小球
思路:
题解
本质就是类似数字三角形的递推
因为存在空缺的位置,从讨论当前状态,往后转移比较好
这种求概率的思路:
概率
d
p
dp
dp
求总方案数,然后
合
法
方
案
数
/
总
方
案
数
合法方案数/总方案数
合法方案数/总方案数 就好了
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
/*
数字三角形
*
* .
* * *
* . * *
* * * * *
*/
char a[maxn][maxn];
ll f[maxn][maxn];
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= i; ++j)
cin >> a[i][j];
f[1][1] = 1;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= i; ++j)
{
if(a[i][j] == '*')// 当前状态往后转移比较好写
{
f[i+1][j] += f[i][j];
f[i+1][j+1] += f[i][j];
}
else f[i+2][j+1] += f[i][j] * 4;
}
}
ll sum = 0;
++m;++n;// 细节,答案在 n+1 层,m是从0开始的
for(int i = 1; i <= n; ++i) sum += f[n][i];
ll d = __gcd(f[n][m], sum);
if(d > 1) f[n][m] /= d, sum /= d;
cout << f[n][m] << "/" << sum;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
小A买彩票
题意:
四种彩票金额分别为
1
,
2
,
3
,
4
1,2,3,4
1,2,3,4 元,买一张需要
3
3
3 元,连续买了
n
n
n 天后不亏本的概率是多少
思路:
转化题目问题,求满足
n
n
n 天后获奖金额
>
=
3
∗
n
>=3*n
>=3∗n 的方案数
code:
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll f[39][129]; // 对于前i张中奖j元的方案数
void work()
{
cin >> n;
f[0][0] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= 4 * i; ++j)
{
if(j >= 1) f[i][j] += f[i-1][j-1];
if(j >= 2) f[i][j] += f[i-1][j-2];
if(j >= 3) f[i][j] += f[i-1][j-3];
if(j >= 4) f[i][j] += f[i-1][j-4];
}
ll ans = 0;
for(int i = 3 * n; i <= 4 * n; ++i)
ans += f[n][i];
ll sum = (1ll << (n << 1));
ll d = __gcd(sum, ans);
sum /= d; ans /= d;
cout << ans << "/" << sum << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
购物
题意:
一个糖果工厂每天生产
m
m
m 个糖果,矩阵中
a
i
,
j
a_{i,j}
ai,j 表示 第
i
i
i 天,第
j
j
j 个糖果的价格。每天可以买至多
m
m
m 个糖果(每种买一个,或者不买),买的糖果可以当天不吃,但要保证每天至少吃一个糖果。
某一天买了
k
k
k 个糖果需要多支付
k
2
k^2
k2 元
求最小花费
思路:
背包dp
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 天买了
j
j
j 个糖果,需要保持
i
<
=
j
i<=j
i<=j
我们没必要知道每个糖果的价格,每次买
k
k
k 个糖果,那么只需要价格最小的
k
k
k 个即可
预处理前缀和
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 3e2 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll c[maxn][maxn];
ll f[maxn][maxn];// f[i][j] 表示前i天买了j个糖果,需要一直满足 i <= j
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= m; ++j)
cin >> c[i][j];
// 非常nice的处理
sort(c[i] + 1, c[i] + 1 + m);
for(int j = 1; j <= m; ++j)
c[i][j] += c[i][j-1];
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for(int i = 1; i <= n; ++i)
for(int j = i; j <= maxn - 5; ++j)
for(int k = 0; k <= min(1ll*j, m); ++k)
f[i][j] = min(f[i][j], f[i-1][j-k] + c[i][k] + k * k);
ll ans = INF;
for(int i = n; i <= maxn - 5; ++i)
ans = min(ans, f[n][i]);
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
Palindrome
这题假了??????没这个题了
题意:
给定一个字符串,要求通过插入字符的操作,使其变成回文串,所需要插入的最少的字符个数。
思路:
思路一:求原串与反转串的最长公共子序列,答案就是
n
−
L
C
S
n-LCS
n−LCS
思路二:
容易考虑区间
[
i
,
j
]
[i,j]
[i,j],容易想到状态转移方程
s
[
i
]
=
s
[
j
]
s[i]=s[j]
s[i]=s[j] 时,
f
[
i
]
[
j
]
=
f
[
i
+
1
]
[
j
−
1
]
f[i][j]=f[i+1][j-1]
f[i][j]=f[i+1][j−1]
s
[
i
]
!
=
s
[
j
]
s[i]!=s[j]
s[i]!=s[j] 时,
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
+
1
]
[
j
]
,
f
[
i
]
[
j
−
1
]
)
+
1
f[i][j]=min(f[i+1][j],f[i][j-1])+1
f[i][j]=min(f[i+1][j],f[i][j−1])+1
但这题不能用区间dp,仔细想想这题是回文串,小区间是无法递推出大区间的
正确思路是左端点从
n
n
n 开始枚举,右端点从
1
1
1 开始枚举,两层
f
o
r
for
for 转移
因为
i
i
i 需要从
i
+
1
i+1
i+1 递推来,
j
j
j 需要从
j
−
1
j-1
j−1 递推来
注意可以
i
i
i 只与前一维有关,可以滚动数组
j
j
j 虽然也只与前一维有关,但是应该不能压了
code1:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e3 + 3;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
string s;
short f[maxn][maxn];
void work()
{
cin >> n >> s;
string t = s;
reverse(t.begin(), t.end());
s = "@" + s; t = "@" + t;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if(s[i] == t[j])
f[i][j] = f[i-1][j-1] + 1;
else
f[i][j] = max(f[i-1][j], f[i][j-1]);
cout << n - f[n][n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
code2:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e3 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
int f[2][maxn];
void work()
{
string s;cin >> n >> s;
s = "@" + s;
int op = 1;
for(int i = n; i >= 1; --i, op ^= 1)
for(int j = i + 1; j <= n; ++j)
if(s[i] == s[j])
f[op][j] = f[op ^ 1][j-1];
else
f[op][j] = min(f[op ^ 1][j], f[op][j-1]) + 1;
cout << f[op ^ 1][n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
Cheapest Palindrome–poj–3280
(这道题虽然不是这个专题的,但是这道题和上边的题十分相似,就搬来了
题意:
给你一串字符串,可以插入或者删除,将之变为回文数。插入和删除每个字符都有代价,问你代价和最小的为多少。
思路:
code: