标签:Atcoder typedef int 题解 EF ++ long leq define
ABC 159
E. Dividing Chocolate (二进制枚举,观察数据范围)
题意
有 \(H\times W\) 大小的方格矩阵,每个方格为 \(0\) or \(1\), 现在可以进行横切与竖切,询问最少切多少次可以保证最后的分块中每个分块都有不超过 \(K\) 个 \(1\)。
数据范围
\(1\leq H \leq 10\)
\(1\leq W \leq 1000\)
\(1\leq K \leq H\times W\)
思路
- 观察数据范围, \(1\leq H \leq 10\) , 所以尝试枚举横切,复杂度 \(O(2^H)\) 。
- 之后我们只关注竖切。从左往右依次遍历,贪心来切一定是最优的。
- 总复杂度 \(O(2^H*H*W)\)$
Solution
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
char S[11][1010];
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int h, w, k;
cin >> h >> w >> k;
for (int i = 0; i < h; i++)
for (int j = 0; j < w; j++)
cin >> S[i][j];
int ans = 1e9;
for (int s = 0; s < 1 << (h - 1); s ++) {
int cnt = __builtin_popcount(s);
int init = cnt + 1;
vector<int> v(init, 0);
bool ok = true;
for (int j = 0; j < w; j++) {
int num = 0;
vector<int> tmp(init, 0);
bool cut = false;
for (int i = 0; i < h; i++) {
if (S[i][j] == '1')
tmp[num]++;
if (tmp[num] + v[num] > k)
cut = true;
if (s >> i & 1)
num++;
}
if (cut) cnt++;
for (int i = 0; i < init; i++) {
if (cut) v[i] = tmp[i];
else v[i] += tmp[i];
if (v[i] > k) ok = false;
}
if (!ok) break;
}
if (ok) ans = min(ans, cnt);
}
cout << ans << endl;
return 0;
}
F. Knapsack for All Segments (非典型DP,DP优化)
题意
给了长度为 \(N\) 的数组 \(A\), 和一个正数 \(S\) 。
定义 \(f(L,R),1\leq L\leq R\leq N\) 为合法序列 \((x_1,x_2,...x_k)\) 的方案数:
求 \(\Sigma f(L,R) \mod 998244353\)
数据范围
\(1\leq N,S,A_i \leq 3000\)
思路
- 数据范围意味着我们可以接受 \(O(n^2)\) 的算法,并且如果 dp 的话大概率是可以把序列和 \(S\) 给放进状态里去的。
- 考虑DP的话,很容易想出时空复杂度为 \(O(n^2*S)\) 的状态, \(f[l][r][x]\) 代表区间 \([l,r]\) 和位 \([x]\) 的方案数,转移可以 \(O(1)\) 类似区间DP的想法,但显然无论时间还是空间都爆掉了,考虑优化。
- 按照题目意思,如果区间 \([l,r]\) 刚好能选出满足要求的数,对答案的贡献是 \((n-r+1)*l\), 由乘法原理得到。
- 一个小trick, 算trick嘛?应该算。考虑去掉 l,r 其中一维,这里取掉其中 1 维。同时状态值的含义也要改变。将答案状态空间按照要选取的 \(x\) 序列最右端点为不同的 \(r\) ,进行不重不漏的划分。状态值改为记录贡献相关数值的形式来计算答案。
- 状态表示: \(f[r][x]\) 表示最右端点为 \(r\) (不一定必选),左端点的下标和为 \(x\) 的方案数。普遍的来说,答案的表示则为 \(\Sigma_{i=1}^n f[i-1][s-a[i]]\) ,这样的方式能表达出每个点成为序列的最右端点的所有子集贡献。
- 状态转移:
- 枚举所有 \(0\leq k\leq s\)
- 如果 \(a[i] < k\), \(f[i][k] = f[i - 1][k - a[i]] + f[i - 1][k]\) 。
- 如果 \(a[i] == k\),\(f[i][k] = i + f[i - 1][k]\)
- 如果 \(a[i] > k\) \(f[i][k] = f[i - 1][k]\)
- 这道题答案不由状态直接表达,而是通过另一种方式间接计算,十分奇妙,值得多复习。时间复杂度为 \(O(s*n)\)
Solution
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int N = 3010, mod = 998244353;
ll f[N][N];
void add(ll & a, ll b) {
a = (a + b) % mod;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, s;
cin >> n >> s;
vector<int> a(n + 1, 0);
for (int i = 1; i <= n; i++)
cin >> a[i];
f[0][0] = 1;
ll ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] < s) add(ans, f[i - 1][s - a[i]] * (n - i + 1));
else if (a[i] == s) add(ans, i * (n - i + 1));
for (int k = s; k; k--) {
f[i][k] = f[i - 1][k];
if (a[i] < k) {
add(f[i][k], f[i - 1][k - a[i]]);
}
else if (a[i] == k) {
add(f[i][k], i);
}
}
}
cout << ans << endl;
return 0;
}
标签:Atcoder,typedef,int,题解,EF,++,long,leq,define 来源: https://www.cnblogs.com/Roshin/p/ABC159.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。