2023 ICPC 横滨区域赛题解 更新至 7 题 (2023-2024 ICPC, Asia Yokohama Regional Contest 2023)

Preface

是谁开场写个签到题都差点红温了,我不说。

好久没打区域赛了,v的这场难度其实比正常的区域赛要简单一点点,但是后面的题阿巴该死的还是屁都不会做啊。

看了2小时的E题,只能说确实比较有难度,属于那种干坐着想不出来,看了题解恍然大悟的程度。

G题也没有怎么看,其实G比E要简单一些,但是当时被E题给破防了,G题知道了题意但是毫无战意。

其他的题倒是还好。思维上我是个弱智。

我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.

以下是代码火车头:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;

template<typename T>
void cc(const vector<T> &tem) {
    for (const auto &x: tem) cout << x << ' ';
    cout << endl;
}

template<typename T>
void cc(const T &a) { cout << a << endl; }

template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }

template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }

void cc(const string &s) { cout << s << endl; }

void fileRead() {
#ifdef LOCALL
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\out.txt", "w", stdout);
#endif
}

void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }

inline int max(int a, int b) {
    if (a < b) return b;
    return a;
}

inline double max(double a, double b) {
    if (a < b) return b;
    return a;
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

inline double min(double a, double b) {
    if (a < b) return a;
    return b;
}

void cmax(int &a, const int &b) { if (b > a) a = b; }
void cmin(int &a, const int &b) { if (b < a) a = b; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;

Problem A. Yokohama Phenomena

哥们,不会有人dfs不会吧

签到题写一个easy的dfs就好了,不过签到就写dfs的倒是挺少见的。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
char A[11][11];
string to = "YOKOHAMA";
int Q[4] = { 1,0,-1,0 };
int W[4] = { 0,1,0,-1 };
int ans;
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
void dfs(int x, int y, int step) {
    if (step == 8) {
        ans++;
        return;
    }
    rep(k, 0, 3) {
        int tx = x + Q[k], ty = y + W[k];
        if (tx<1 or ty<1 or tx>n or ty>m) continue;
        if (A[tx][ty] != to[step]) continue;
        dfs(tx, ty, step + 1);
    }
}
 
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
        rep(i, 1, n) {
            rep(j, 1, m) {
                cin >> A[i][j];
            }
        }
        rep(i, 1, n) {
            rep(j, 1, m) {
                if (A[i][j] == to[0]) {
                    dfs(i, j, 1);
                }
            }
        }
        cc(ans);
 
    }
    return 0;
}

Problem B. Rank Promotion

先设置来\(pre_i\)是前\(i\)个里面Y的个数,那么我们如果希望第i项是可以升级的,那么会有。

\[(pre_i-pre_j)/(i-j)>=p/q \]

j的范围可以推断出来在一个区间里面,然后这个式子我们变化一下:(这里a是\(p/q\),我懒得写了)

\[pre_i-a*i>=pre_j-a*j \]

我们可以用存一下这个的形式的最小值,然后只要有存在比它大的,就代表我们可以升级。

貌似其实有更简单的实现办法,可能当时想复杂了。

代码是队友实现的:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

const int N=500005;
const int INF=1e18;

int n,c,p,q;
int rk;
int lst;
int ny;

string str;
int pre[N];

int mnp;

int val(int x){
    return pre[x]*q-p*x;
}

void solve(){
    cin>>n>>c>>p>>q>>str;
    str='*'+str;
    for(int i=1;i<=n;++i)
        pre[i]=pre[i-1]+(str[i]=='Y');

    lst=mnp=0;
    for(int i=1;i<=n;++i){
        if(i-lst<c)continue;
        //insert i-c
        if(val(i-c)<val(mnp))
            mnp=i-c;
        if(val(i)>=val(mnp)){
            rk++;
            lst=i;
            mnp=i;
        }
    }   
    cout<<rk<<endl;
    return;
}

signed main(void){
    ios::sync_with_stdio(false),cin.tie(0);
    //int T; cin>>T; while(T--)
    solve();
    return 0;
}

Problem D. Nested Repetition Compression

非常典的区间dp啊。

我们直接设\(dp_{l,r}\)是区间从l到r之间可以构造出来的最短的字符串,那么我们转移有两种办法,要么是直接枚举中间的断点mid,要么就是当前这个区间可以直接进行缩写,也就是说我们直接枚举倍数就好了,前者的复杂度是n,后者的复杂度肯定也不会超过n,所以总体的复杂度就是n的3次方那里。

然后我们判断可否可以分割成k个相同的字符串这里我写了个哈希。

//--------------------------------------------------------------------------------
const int N = 2e2 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:
class HASH {
    int base = 131;
    int P[2][N], pre[2][N];
    char S[N];
    int Mod[2];
    int n;
    bool REVERSE;

public:
    void clear(const string& s, bool fl = 0) {
        Mod[0] = 998244353;
        Mod[1] = 1e9 + 7;
        n = s.size(), REVERSE = fl;
        if (REVERSE)
            for (int i = 1; i <= n; i++) S[i] = s[n - i];
        else
            for (int i = 1; i <= n; i++) S[i] = s[i - 1];
        P[0][0] = P[1][0] = 1;
        pre[0][0] = 0;
        for (int i = 0; i <= 1; i++) {
            for (int j = 1; j <= n; j++) {
                pre[i][j] = (pre[i][j - 1] * base % Mod[i] + S[j]) % Mod[i];
                P[i][j] = P[i][j - 1] * base % Mod[i];
            }
        }
    }

    PII qry(int l, int r) {
        int a, b;
        l++, r++;
        if (REVERSE) l = n + 1 - l, r = n + 1 - r, swap(l, r);
        a = (pre[0][r] - pre[0][l - 1] * P[0][r - l + 1] % Mod[0] + Mod[0]) % Mod[0];
        b = (pre[1][r] - pre[1][l - 1] * P[1][r - l + 1] % Mod[1] + Mod[1]) % Mod[1];
        return { a, b };
    }
};
HASH hs; string s;
//--------------------------------------------------------------------------------
bool fl[N][N];
string dp[N][N];

string dfs(int l, int r) {

    if ((l == r) or (l + 1 == r)) {
        string tem = s.substr(l, r - l + 1);
        return tem;
    }

    if (fl[l][r]) {
        return dp[l][r];
    }
    fl[l][r] = 1;
    string tem = s.substr(l, r - l + 1);
    string mmin = "";
    rep(mid, l, r - 1) {
        if (mmin == "") mmin = dfs(l, mid) + dfs(mid + 1, r);
        else {
            string tem2 = dfs(l, mid) + dfs(mid + 1, r);
            if (tem2.size() < mmin.size()) mmin = std::move(tem2);
        }
    }
    if (mmin.size() < tem.size()) tem = mmin;

    rep(k, 2, r - l + 1) {
        if ((r - l + 1) % k != 0) continue;
        int len = (r - l + 1) / k;
        if (k > 9) break;
        PII haxi = hs.qry(l, l + len - 1);
        bool flag = 1;
        for (int i = l + len - 1; i <= r; i += len) {
            PII haxi2 = hs.qry(i - len + 1, i);
            if (haxi2 != haxi) {
                flag = 0;
                break;
            }
        }
        if (flag == 1) {
            string s = to_string(k) + '(' + dfs(l, l + len - 1) + ')';
            if (s.size() < tem.size()) tem = s;
        }
    }

    dp[l][r] = tem;

    return tem;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> s;
        hs.clear(s);
        n = s.size();
        cc(dfs(0, n - 1));
    }
    return 0;
}


Problem E. Chayas

md看了好久但是还是不会做的题。哎,还是太菜了。

能想得到对于每一个中间的点b我们可以看做是一个图,把a和c连上边,其实他们是构成像二分图一样(实际不是)的东西,然后再写写画画我们可以知道,每一个相邻之间的他们都一定是属于不同的地方,例如如果1--3--5--2,那么1和3肯定是在一侧,5和2就一定是在相对的另一侧,也就是说我们用dfs跑出来他们,给他们染上色。

无解肯定就是染色失败的情况。

有个细节的地方是:这个图不一定是联通的,会有多个联通块,那在一侧的结果就会是这些多个联通块的他们的组合,每一个联通块的颜色染成0的部分称为\(s_{i,0}\),染成1的称为\(s_{i,1}\),那么\(s_{i,1}\)可以和\(s_{j,0}\)或者是\(s_{j,1}\)去组合,这里我们也可以直接dfs2递归处理。

之后就是没有想到的地方了,我们可以用状压dp去做,当前状态称之为S,代表我们已经放进去的点集,那么我们如何判断当前一个点i可不可以放进去,只需要知道这个点放进去的时候可以允许的点集,我们刚才dfs2其实就是可以处理当前一个点放进去的时候可以允许的点集了,所以就直接跑一个状态转移就好了。

小细节是:我们要转移的时候,要保证状态S1的1的个数是大于状态S2的1的个数的,也就是我们要从1的个数少的开始枚举,逐渐多起来。

//--------------------------------------------------------------------------------
const int N = 24 + 10;
const int M = 16777216 + 10;
// const int M = 10000;
const int mod = 998244353;
// const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//global variable:

//struct or namespace:

//--------------------------------------------------------------------------------
vec<int> A[N][N];
bitset<N> co;
int id[N];
int idx;
bitset<M> flag[N];
// bool flag[M][N];
int dp[M];
int cnt[2][N];
vec<int> ge[N];


bool dfs(int rt, int x, int cur) {
    // cc(rt, x, cur);
    id[x] = cur;
    for (auto y : A[rt][x]) {
        if (id[y] > 0) {
            if (co[y] != (co[x] ^ 1)) return false;
            continue;
        }
        co[y] = (co[x] ^ 1);
        if (!dfs(rt, y, cur)) return false;
    }
    return true;
}

void dfs2(int rt, int cur, int S) {
    if (cur >= idx + 1) {
        flag[rt][S] = 1;
        // cc(rt, S);
        return;
    }
    dfs2(rt, cur + 1, S | (cnt[0][cur]));
    dfs2(rt, cur + 1, S | (cnt[1][cur]));
}

bool work(int x) {

    idx = 0;
    rep(i, 1, n) {
        id[i] = 0, co[i] = 0;
        cnt[0][i] = cnt[1][i] = 0;
    }
    //memset(cnt[0], 0, sizeof(cnt[0]));
    //memset(cnt[1], 0, sizeof(cnt[1]));


    rep(i, 1, n) {
        if (i == x) continue;
        if (id[i] > 0) continue;

        co[i] = 0, ++idx;
        if (!dfs(x, i, idx)) return false;
    }


    rep(i, 1, n) {
        if (i == x) continue;

        cnt[co[i] == 1][id[i]] |= (1 << (i - 1));
    }

    dfs2(x, 1, 0);

    return true;

}

signed main() {
    // fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
        rep(i, 1, m) {
            int a, b, c;
            cin >> a >> b >> c;
            A[b][a].push_back(c);

            A[b][c].push_back(a);
        }
        // cc(222);
        bool fl = 1;
        rep(i, 1, n) {
            if (work(i) == 0) {
                fl = 0;
                break;
            }
        }
        if (fl == 0) {
            cc(0);
            continue;
        }
        int lim = 1 << n;
        lim -= 1;
        // cc(111);
        rep(i, 0, n) ge[i].clear();
        rep(i, 0, lim) {
            ge[__popcount(i)].push_back(i);

        }
        dp[0] = 1;
        rep(i, 0, n) {
            if (ge[i].empty()) continue;
            for (auto S : ge[i]) {
                rep(j, 1, n) {
                    // cc("--");
                    if (flag[j][S] == 0) continue;
                    int y = S | (1ll << (j - 1));
                    dp[y] = (long long)(dp[y] + dp[S]) % mod;
                    // dp[y] += dp[x];
                }

            }
        }
        cc(dp[lim]);
        // cc("END");
    }
    return 0;
}

/*


*/

Problem F. Color Inversion on a Huge Chessboard

狗屎题,我吃屎了兄弟们。

一个简单的分类讨论给我写成了屎一样的东西。

这个问题我们可以考虑成,假设一开始矩阵全是0,可以行或者列操作,最后会有多少的联通块。

分开考虑行和列,分别维护数组,0代表没操作,1代表操作了。最后数组的联通块的乘积就是答案。

如何变成问题开始的那样呢?我们只需要对行0,1,0,1这样的操作就可以了,列也是这样。

代码不想改了,又臭又长。

 
//--------------------------------------------------------------------------------
const int N = 5e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int row[N], col[N];
int rl, cl;
//--------------------------------------------------------------------------------
//struct or namespace:
 
 
//100000000 
 
//--------------------------------------------------------------------------------
// unordered_map<string, int> mp;
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
 
        if (n == 1) {
 
            rep(i, 1, m) {
                string s; cin >> s;
                int id; cin >> id;
                cout << 1 << endl;
            }
            continue;
        }
 
        int ans = 0;
        rl = n, cl = n;
        rep(i, 1, n) {
            row[i] = i % 2, col[i] = i % 2;
        }
        rep(i, 1, m) {
            string s; cin >> s;
            int id; cin >> id;
            if (s == "ROW") {
                int newrl = rl, newcl = cl;
 
 
                if (id == 1 or id == n) {
 
                    if (id == 1) {
                        if (row[id + 1] == row[id]) newrl += 1;
                        else newrl -= 1;
                    }
                    else
                        if (id == n) {
                            if (row[id - 1] == row[id]) newrl += 1;
                            else {
                                newrl -= 1;
                            }
                        }
                    row[id] ^= 1;
                    ans = newrl * newcl;
 
                    rl = newrl, cl = newcl;
                    cc(ans);
 
                    continue;
                }
                if (row[id - 1] == row[id] and row[id] == row[id + 1]) {
                    newrl += 2;
                }
                else if (row[id - 1] == row[id + 1] and row[id] != row[id + 1]) {
                    newrl -= 2;
                }

                row[id] ^= 1;
 
                ans = newrl * newcl;
                cc(ans);
 
                rl = newrl, cl = newcl;
 
 
            }
            else {
                int newrl = rl, newcl = cl;
 
                if (id == 1 or id == n) {
 
                    if (id == 1) {
                        if (col[id + 1] == col[id]) newcl += 1;
                        else newcl -= 1;
                    }
                    else
                        if (id == n) {
                            if (col[id - 1] == col[id]) newcl += 1;
                            else {
                                newcl -= 1;
                            }
                        }
                    col[id] ^= 1;
                    ans = newrl * newcl;
 
                    rl = newrl, cl = newcl;
                    cc(ans);
 
                    continue;
                }
                if (col[id - 1] == col[id] and col[id] == col[id + 1]) {
                    newcl += 2;
                }
                else if (col[id - 1] == col[id + 1] and col[id] != col[id + 1]) {
                    newcl -= 2;
                }

                col[id] ^= 1;
                ans = newrl * newcl;
 
                cc(ans);
                rl = newrl, cl = newcl;
 
            }
        }
    }
    return 0;
}

Problem G. Fortune Telling

一个看似比较困难,但是其实还好的难度题,估计难度应该是铜或者铜-。

难点其实在于写dp。

设计\(dp_{n,k}\),代表当前有\(n\)个数,第\(k\)个数字的存活概率是多少。

那么我们的一次操作会带来的影响就是会减少大概\((n/6)\)个数字,所以我们最多操作的次数其实就没有多少次。开一个unordered_map比较合适。

假设扔筛子的点数是i,那么如果\((n%6)\)>\(=i\),就代表是减少了\(n/6+1\)个数字,否则就是减少了\(n/6\)个数字。\(k\)也是这样的类似的考虑。

来一个记忆化搜索。

//--------------------------------------------------------------------------------
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int mod = 998244353;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------
unordered_map<int, int> dp[N];
int fenmu[7];
int dfs(int n, int k) {
    if (n == 1 and k == 0) return 1;
    if (dp[n].count(k)) {
        return dp[n][k];
    }
    dp[n][k] = 0;
    rep(i, 0, min(6, n) - 1) {
        if (k % 6 == i) continue;
        int tn = n - n / 6, tk = k - k / 6;
        if (n % 6 > i) tn--;
        if (k % 6 > i) tk--;
        dp[n][k] += dfs(tn, tk) * fenmu[min(6, n)] % mod;
        dp[n][k] %= mod;
    }
    return dp[n][k];
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;


    fenmu[0] = 1;
    rep(i, 1, 6) fenmu[i] = Kuai<mod>(i, mod - 2);
    while (T--) {
        cin >> n;
        rep(i, 0, n - 1) {
            cc(dfs(n, i));
        }
    }
    return 0;
}

/*


*/

Problem K. Probing the Disk

这个题也比较恶心,不能测试,大概只能自己感觉对了就交,直接变成IOI。

思路是我们一开始先横着横着查,间隔是199,这样我们就一定可以查出来这个圆的y坐标是多少。

查到了之后我们就可以根据当前的点进行二分,二分的间隔是是否有覆盖。这样好些一些,如果是二分的覆盖线段的长度,估计有点麻烦。

然后竖着也是这样做的,但是却WA了。估计是勉勉强强超过了1024,感觉二分的长度是直径的话,也该能过,看到有人是这样做过了的。

张神的思路:横着操作之后,y轴我们固定是在圆的中间的位置。我们就二分覆盖线段的长度是直径,这样操作次数非常的放心。

代码是队友张神写的。

#include<bits/stdc++.h>
#define endl '\n'
#define mid ((L+R)/2)
#define ll long long
using namespace std;

const int N=100000;
const double eps=1e-6;

int hit;
int radius;
int hh,vv;

bool query_h(int i){
    cout<<"query 0 "<<i<<" 100000 "<<i<<endl;
    cout.flush();
    double x; cin>>x; return fabs(x)>=eps;
}
bool query_v(int i){
    cout<<"query 0 "<<hh<<' '<<i<<' '<<hh<<endl;
    cout.flush();
    double x; cin>>x; return (x>=2*radius-eps);
}

int solve_h(){
    for(int i=199;i<=N;i+=199){
        if(query_h(i)){
            hit=i;
            break;
        }
    }

    int L,R,up,down;
    
    L=hit; R=N;
    while(L<=R){
        if(query_h(mid)){
            up=mid;
            L=mid+1;
        }else{
            R=mid-1;
        }
    }

    L=0; R=hit;
    while(L<=R){
        if(query_h(mid)){
            down=mid;
            R=mid-1;
        }else{
            L=mid+1;
        }
    }
    up++; down--;
    radius=(up-down)/2;
    return (up+down)/2;
}

int solve_v(){
    int L,R,ans;
    L=0; R=N;
    while(L<=R){
        if(query_v(mid)){
            ans=mid;
            R=mid-1;
        }else{
            L=mid+1;
        }
    }
    return ans-radius;
}

void solve(){
    hh=solve_h();
    vv=solve_v();
    cout<<"answer "<<vv<<' '<<hh<<' '<<radius<<endl;
    cout.flush();
}

signed main(void){
    //ios::sync_with_stdio(false),cin.tie(0);
    //int T; cin>>T; while(T--)
    solve();
    return 0;
}

PostScript

这把打得感觉不是太妙。

队友少了一个,西安邀请赛要双开了,感觉非常的害怕。

于是害怕的开始耍。

posted @ 2025-04-24 17:22  AdviseDY  阅读(88)  评论(0)    收藏  举报