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;
}
posted @ 2020-03-25 10:25  heyuhhh  阅读(259)  评论(0编辑  收藏  举报