Educational Codeforces Round 84 (Rated for Div. 2)
A. Sum of Odd Integers
\(k\)个不同奇数和的最小值为\(k^2\),那么必须满足:
- \(k,n\)同奇偶;
- \(k^2\leq n\)。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/23 22:35:41
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
void run() {
int n, k; cin >> n >> k;
int sq = 1;
while(sq * sq <= n) ++sq;
--sq;
if(k <= sq && k >= 2 - (n & 1)) {
int r = sq - k + 1;
int rr = n - sq * sq;
if((r & 1) != (rr & 1)) cout << "YES" << '\n';
else cout << "NO" << '\n';
} else cout << "NO" << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
B. Princesses and Princes
贪心。
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/23 22:56:10
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int n;
bool chk[N];
void run() {
cin >> n;
vector <vector <int>> a(n);
for(int i = 0; i < n; i++) {
int k; cin >> k;
a[i].resize(k);
for(int j = 0; j < k; j++) cin >> a[i][j];
}
for(int i = 0; i <= n; i++) chk[i] = false;
int cnt = 0;
vector <int> r;
for(int i = 0; i < n; i++) {
bool f = false;
for(int j = 0; j < sz(a[i]); j++) {
if(!chk[a[i][j]]) {
f = true;
chk[a[i][j]] = true;
++cnt;
break;
}
}
if(f == false) r.push_back(i);
}
if(cnt == n) cout << "OPTIMAL" << '\n';
else {
cout << "IMPROVE" << '\n';
cout << r[0] + 1 << ' ';
for(int i = 1; i <= n; i++) if(!chk[i]) {
cout << i << '\n';
break;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
C. Game with Chips
先走到左上角,然后依次遍历整个网格就行。
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/23 23:07:11
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int n, m, k;
void run() {
cin >> n >> m >> k;
for(int i = 1; i <= k; i++) {
int x, y; cin >> x >> y;
}
for(int i = 1; i <= k; i++) {
int x, y; cin >> x >> y;
}
string res = "";
for(int i = 0; i < n - 1; i++) res += "U";
for(int i = 0; i < m - 1; i++) res += "L";
for(int i = 0; i < n; i++) {
if(i & 1) {
for(int j = 0; j < m - 1; j++) res += "L";
} else {
for(int j = 0; j < m - 1; j++) res += "R";
}
if(i < n - 1) res += "D";
}
cout << res.length() << '\n';
cout << res << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
D. Infinite Path
题意:
给出一个\(1...n\)的排列\(p\),第\(i\)个位置有颜色\(c_i\)。
定义无穷路径:\(i,p[i],p[p[i]]...\)都有相同的颜色。
定义\(p\times p=p[p[i]]\),类似可以定义\(p^k\)。
现在问找到最小的一个\(k,k>0\),满足\(p^k\)至少存在一条无穷路径。
思路:
连边\(i\rightarrow p[i]\),那么最终会有若干个环,\(p^k\)每个位置的值即是在环上每个点走\(k\)步到达的值。
存在一条无穷路径即可以转化为:存在一个点走若干次,每次走\(k\)步,最终能回到出发点,且所有到达的点的颜色都相同。
那么对于每个环根据其长度找到约数,然后单独计算即可。
约数的个数应该是\(O(n^{\frac{1}{3}})\)级别的,时间复杂度不超过\(O(n^{\frac{4}{3}})\)。
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/24 0:28:41
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 2e5 + 5;
int n;
int p[N], pos[N], c[N];
bool chk[N];
vector <int> G[N];
vector <int> v;
void dfs(int u) {
chk[u] = true;
v.push_back(c[pos[u]]);
for(auto to : G[u]) if(!chk[to]) dfs(to);
}
int solve(int k) {
for(int i = 0; i < k; i++) {
bool ok = true;
for(int j = i + k; j < sz(v); j += k) {
if(v[j - k] != v[j]) ok = false;
}
if(ok) return k;
}
return n;
}
void run() {
cin >> n;
for(int i = 1; i <= n; i++) G[i].clear(), chk[i] = false;
for(int i = 1; i <= n; i++) {
cin >> p[i]; pos[p[i]] = i;
G[i].push_back(p[i]);
}
for(int i = 1; i <= n; i++) cin >> c[i];
int ans = n;
for(int i = 1; i <= n; i++) if(!chk[i]) {
v.clear();
dfs(i);
int k = sz(v);
for(int j = 1; j * j <= k; j++) if(k % j == 0) {
ans = min(ans, min(solve(j), solve(k / j)));
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
E. Count The Blocks
枚举每个\(block\)的长度为\(1,2,\cdots,n\),然后依次计算答案。
对于每个枚举的长度\(x\),分情况这一段左右两边是否为合法位置计算就行。
详见代码:
Code
F. AND Segments
题意:
给出\(n,k,m\),其中有\(m\)个限制\((l_i,r_i,x_i)\),现在要回答序列\(a\)的个数且要满足以下条件:
- \(0\leq a_i<2^k\);
- 对于\(1\leq i\leq m\),\(a[l_i]\&\cdots \&a[r_i]=x_i\)。
思路:
- 可以按位分别进行考虑。
- 对于每一位,限制条件则为存在某些区间,这上面的数必须为\(1\);存在另一些区间,二进制位上至少有一个\(0\)。
- 显然我们可以想到一个\(dp:dp_{i,j}\)表示考虑了前面\(i\)个位置,最后一次\(0\)出现在\(j\)位置,目前满足所有限制条件右端点不超过\(i\)的方案数。
- 转移分情况考虑:
- 若当前位为\(0\),那么\(dp_{i,0}=\sum_{j=0}^{i-1} dp_{i-1,j}\);
- 若当前位为\(1\),因为我们要满足至少有一个是\(0\)的限制条件,考虑以\(i\)为右端点的限制区间,假设区间左端点最大值为\(k\),那么\(dp_{i,j}=dp_{i-1,j},j\geq k\)。如果此时从小的地方转移过来会出现连续的\(1\)不满足条件。
- 以上\(dp\)的时间复杂度为\(O(n^2)\),通过线段树优化则可以达到\(O(nlogn)\)。接下来考虑进一步优化。
- 记\(pre_i\)表示以\(i\)为右端点的询问区间最大的左端点的值,且满足\([pre_i,i]\)至少有一个\(0\)。那么有个限制条件即为\(pre_i\geq pre_{i-1}\)。
- 注意到\(dp\)转移式中,\(dp_i\)的值都是从\(i-1\)复用,但是当当前这一位为\(1\)时\([pre_{i-1},pre[i]-1]\)这段区间我们必须舍弃,然而对于每个\(i\)这个过程是单调的,所以用一个指针维护一下就行。
后面\(dp\)的优化挺巧妙的,主要是基于状态之间的复用这一性质,\(dp_i\)的值都来自于\(dp_{i-1}\)。所以才能根据后面转移位置的单调性来进行优化。
代码就比较简单:
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/24 17:50:08
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5e5 + 5, MOD = 998244353 ;
int n, k, m;
int l[N], r[N], x[N];
int pre[N], cnt[N];
int dp[N];
int solve(int bit) {
dp[0] = 1;
memset(pre, 0, sizeof(pre));
memset(cnt, 0, sizeof(cnt));
for(int i = 1; i <= m; i++) {
if(x[i] >> bit & 1) ++cnt[l[i]], --cnt[r[i] + 1];
else pre[r[i]] = max(pre[r[i]], l[i]);
}
int s = 1, tail = 0;
for(int i = 1; i <= n; i++) {
cnt[i] += cnt[i - 1];
if(cnt[i]) dp[i] = 0;
else dp[i] = s;
while(tail < pre[i]) {
s -= dp[tail++];
if(s < 0) s += MOD;
}
s += dp[i]; s %= MOD;
}
return s;
}
void run() {
cin >> n >> k >> m;
for(int i = 1; i <= m; i++) {
cin >> l[i] >> r[i] >> x[i];
}
int ans = 1;
for(int bit = 0; bit < k; bit++) {
ans = 1ll * ans * solve(bit) % MOD;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
G. Letters and Question Marks
题意:
给出\(k\)个字符串\(t_i\),所有串长度之和不超过\(1000\),字符集大小为\(14\)。每个字符串有一个代价\(c_i\)。
然后给出一个串\(s\),长度不超过\(4\cdot 10^5\),字符集大小为\(14\),某些位置可能是\(?\),这样的位置数也不超过\(14\)。
现在要给\(?\)位置安排两两不同的字符,问最后总的代价最大为多少。
代价计算方法为:\(\displaystyle\sum_{i=1}^kF(S, t_i)\cdot c_i\),其中\(F(S, t_i)\)为\(t_i\)串在\(S\)中出现的次数。
思路:
- 考虑暴力的方法:\(dp_{i,sta}\)表示前\(i\)个位置,目前确定了的状态为\(sta\)的最大代价,然后对串找子串进行计算。找子串这一过程可以用\(AC\)自动机来,时间复杂度为\(O(n^2\cdot 2^{14}\cdot 14)\)。
- 接下来考虑优化:假设\(s\)只存在一个\(?\),那么我们枚举所有状态时在\(AC\)自动机上面来匹配,每次都会从头开始跑,显然会浪费大量的时间,那么就有一个优化思路:
优化一: 记录每个连续的段在\(AC\)自动机上面的转移。
因为每次遇到一个\(?\)我们会枚举此时所有可能的状态,那么我们需要知道从所有结点出发连续的段在\(AC\)自动机上面的转移。
- 暴力\(dp\)的每次转移,我们都需要找到\(AC\)自动机上面的结点,也就是说其实记录\(s\)串的长度是没有必要的,我们更关心的是\(AC\)自动机上面的结点和状态。那么又有一个优化思路:
优化二: \(dp_{i,sta}\)表示当在\(AC\)自动机上面的\(i\)结点,目前状态为\(sta\)时的最大代价。这样总状态数为\(O(1000\cdot 2^{14})\)。
如果想到这里基本上这个题就出来了,每次遇到\(?\)时就直接在\(AC\)自动机上暴力转移之前连续的段,并更新状态。
什么时候我也能这么思考啊
细节见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/3/25 8:58:08
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 4e5 + 5, M = 1005, MAX = 14;
queue <int> q;
namespace ACAM{
int sz;
int ch[M][MAX];
int cnt[M], fail[M], sum[M];
int newnode() {
memset(ch[++sz], 0, sizeof(ch[sz]));
cnt[sz] = fail[sz] = 0;
return sz;
}
void init() {
sz = -1;
newnode();
}
void insert(char *s, int val) {
int u = 0;
for(int i = 1; s[i]; i++) {
int idx = s[i] - 'a';
if(!ch[u][idx]) ch[u][idx] = newnode();
u = ch[u][idx];
}
sum[u] += val;
}
void build() {
while(!q.empty()) q.pop();
for(int i = 0; i < MAX; i++) {
if(ch[0][i]) q.push(ch[0][i]);
}
while(!q.empty()) {
int cur = q.front(); q.pop();
for(int i = 0; i < MAX; i++) {
if(ch[cur][i]) {
fail[ch[cur][i]] = ch[fail[cur]][i];
sum[ch[cur][i]] += sum[ch[fail[cur]][i]];
q.push(ch[cur][i]);
} else {
ch[cur][i] = ch[fail[cur]][i];
}
}
}
}
};
using namespace ACAM;
int k;
char s[N], t[M];
int go[M];
ll dp[M][1 << MAX], c[M];
void chkmax(ll &x, ll y) {
x = max(x, y);
}
void run() {
cin >> k;
init();
for(int i = 1; i <= k; i++) {
int c;
cin >> (t + 1) >> c;
insert(t, c);
}
build();
for(int i = 0; i <= sz; i++) {
for(int j = 0; j < 1 << MAX; j++) {
dp[i][j] = -1e18;
}
}
dp[0][0] = 0;
for(int i = 0; i <= sz; i++) go[i] = i, c[i] = 0;
cin >> (s + 1);
int cnt = 0;
for(int i = 1; s[i]; i++) {
if(s[i] == '?') {
for(int st = 0; st < 1 << MAX; st++) {
if(__builtin_popcount(st) != cnt) continue;
for(int j = 0; j <= sz; j++) if(dp[j][st] > -1e18) {
for(int t = 0; t < MAX; t++) if(!(st >> t & 1)) {
chkmax(dp[ch[go[j]][t]][st | (1 << t)], dp[j][st] + c[j] + sum[ch[go[j]][t]]);
}
}
}
for(int j = 0; j <= sz; j++) go[j] = j, c[j] = 0;
++cnt;
} else {
for(int j = 0; j <= sz; j++) go[j] = ch[go[j]][s[i] - 'a'], c[j] += sum[go[j]];
}
}
ll ans = -1e18;
for(int st = 0; st < 1 << MAX; st++) {
if(__builtin_popcount(st) == cnt) {
for(int i = 0; i <= sz; i++) {
chkmax(ans, dp[i][st] + c[i]);
}
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。