A. Sum of Odd Integers
(k)个不同奇数和的最小值为(k^2),那么必须满足:
- (k,n)同奇偶;
- (k^2leq 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 << '
'; }
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" << '
';
else cout << "NO" << '
';
} else cout << "NO" << '
';
}
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 << '
'; }
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" << '
';
else {
cout << "IMPROVE" << '
';
cout << r[0] + 1 << ' ';
for(int i = 1; i <= n; i++) if(!chk[i]) {
cout << i << '
';
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 << '
'; }
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() << '
';
cout << res << '
';
}
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 imes p=p[p[i]]),类似可以定义(p^k)。
现在问找到最小的一个(k,k>0),满足(p^k)至少存在一条无穷路径。
思路:
连边(i
ightarrow 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 << '
'; }
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 << '
';
}
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)的个数且要满足以下条件:
- (0leq a_i<2^k);
- 对于(1leq ileq 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},jgeq k)。如果此时从小的地方转移过来会出现连续的(1)不满足条件。
- 以上(dp)的时间复杂度为(O(n^2)),通过线段树优化则可以达到(O(nlogn))。接下来考虑进一步优化。
- 记(pre_i)表示以(i)为右端点的询问区间最大的左端点的值,且满足([pre_i,i])至少有一个(0)。那么有个限制条件即为(pre_igeq 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 << '
'; }
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 << '
';
}
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),长度不超过(4cdot 10^5),字符集大小为(14),某些位置可能是(?),这样的位置数也不超过(14)。
现在要给(?)位置安排两两不同的字符,问最后总的代价最大为多少。
代价计算方法为:(displaystylesum_{i=1}^kF(S, t_i)cdot c_i),其中(F(S, t_i))为(t_i)串在(S)中出现的次数。
思路:
- 考虑暴力的方法:(dp_{i,sta})表示前(i)个位置,目前确定了的状态为(sta)的最大代价,然后对串找子串进行计算。找子串这一过程可以用(AC)自动机来,时间复杂度为(O(n^2cdot 2^{14}cdot 14))。
- 接下来考虑优化:假设(s)只存在一个(?),那么我们枚举所有状态时在(AC)自动机上面来匹配,每次都会从头开始跑,显然会浪费大量的时间,那么就有一个优化思路:
优化一: 记录每个连续的段在(AC)自动机上面的转移。
因为每次遇到一个(?)我们会枚举此时所有可能的状态,那么我们需要知道从所有结点出发连续的段在(AC)自动机上面的转移。
- 暴力(dp)的每次转移,我们都需要找到(AC)自动机上面的结点,也就是说其实记录(s)串的长度是没有必要的,我们更关心的是(AC)自动机上面的结点和状态。那么又有一个优化思路:
优化二: (dp_{i,sta})表示当在(AC)自动机上面的(i)结点,目前状态为(sta)时的最大代价。这样总状态数为(O(1000cdot 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 << '
'; }
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 << '
';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}