复健训练-CF gym103495部分

题目在这

【这场是一个校内训练赛搬来的题】

【纪念一下,把所有题订完了】

【以及,这场训练赛中拿了人生第一个first blood > < ,虽然说那个 C 题要是早点发现错误就会有两个了,【呜呜,可能觉得我拿了一个就会骄傲,就不给我第二个了...】

 

A. Spring Couplets

题意:给你一对春联,给出声调,问你这副春联是否平仄相对,平仄相对的规定是,前面 n-1 个字,上联是平/仄声下联就要是仄/平声,最后一个字必须上联是仄下联是平。平声是 1 2 声,仄声是 3 4 声。(论语文素养的重要性..

做法:看懂题意是关键(直接做就好了

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define ll long long

using namespace std;

const int N = 1005;
int n, a[N], b[N];
char s[N];

void Main(){
    scanf("%d", &n);
    rep (i,1,n){
        scanf("%s", s+1); int l = strlen(s+1);
        a[i]=s[l]-'0';
    }
    rep (i,1,n){
        scanf("%s", s+1); int l = strlen(s+1);
        b[i]=s[l]-'0';
    }
    rep (i,1,n-1){
        if (!(a[i]<=2 && b[i]>=3 || (a[i]>=3 && b[i]<=2)))
            {puts("NO"); return;}
    }
    if (!(a[n]>=3 && b[n]<=2)){puts("NO"); return;} 
    puts("YES");
    return;
}

int main(){
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

B. Among Us

题意:给一张无向图,现在有两个猎人和 k 个猎物,已知 E 个规则,每个规则会告诉你 $p_i$ 这个猎物会在 $x_i$ 点处,在 $t_i$ 的时间出现。猎人可以选择两个动作,一是沿着一条边走到另一个点,花费时间为边权;二是呆在原地等待。如果猎人在的这个点,当前时间恰好有猎物出现,他就可以把该猎物杀掉。现在两个猎人要把所有猎物杀掉,总时间不能超过 $tmax$ ,问最少时间是多少?$n\le 10^4,m\le 2\times 10^4,E\le 10^5,k\le 8$

做法:【题意真的好难读啊...】这个数据范围明显是要状压的。显然可以把两个起点分开考虑,然后状压 dp ,$dp[s][i]$ 表示当前状态是 s ,走到点 i 的最少时间,状态就是抓了哪些猎物。每次转移有两种,第一种是走边,对于这个 s 跑一个最短路即可。第二种是原地转移,就是枚举当前要等待的猎物,当前时间就变成这个猎物的出现时间。最后对于两个起点进行合并,具体可以看代码。

【这个题一开始想法是直接拆点跑 dijkstra ,但是这样好像会 T (自己没试过 > < 大佬讲的),因为第二种转移 E 的范围有$10^5$ ,所以第二种转移边数大概有 $E\times 2^k$ ,dijkstra 的复杂度是 $m \log m$ ,这样算一下复杂度直接爆了,所以不能直接这么多,要按照状态来 dp 】

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define pii pair<int,int>
#define fi first
#define se second

using namespace std;

const int N = 1e4+10, M = 2e4+10, S = 260;
const int inf = 1e9;
int n, m, K, E, s1, s2, cnt, tmax, head[N], dp1[S][N], dp2[S][N], ans1[S], ans2[S], vis[N], ans;
struct edge{
    int to, nxt, w;
}e[M<<1];
vector<pii> V[N];

void adde(int x,int y,int w){
    e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt;
    e[cnt].w=w;
}

priority_queue<pii, vector<pii>, greater<pii> > q;

void trans(int s, int dp[S][N]){
    rep (i,1,n) q.push(pii(dp[s][i], i)), vis[i] = 0;
    while (!q.empty()){
        int u = q.top().se; q.pop(); vis[u] = 1;
        for (int i=head[u]; i; i=e[i].nxt){
            int v = e[i].to;
            if (dp[s][v] > dp[s][u] + e[i].w){
                dp[s][v] = dp[s][u] + e[i].w;
                if (!vis[v]) q.push(pii(dp[s][v], v));
            }
        }
    } //第一种 最短路转移

    rep (i,1,n){
        for (auto x:V[i]){
            int sta = 1<<(x.fi-1);
            int t = x.se;
            if (dp[s][i] <= t) dp[s|sta][i] = min(dp[s|sta][i], t);
        }
    } //第二种 原地转移
}

void solve(int s, int dp[S][N], int *ans){
    rep (i,0,(1<<K)-1){
        ans[i] = inf;
        rep (j,1,n) dp[i][j] = inf;
    }
    dp[0][s] = 0;
    while (!q.empty()) q.pop();
    rep (i,0,(1<<K)-1) trans(i, dp); //转移
    rep (i,0,(1<<K)-1)
        rep (j,1,n) ans[i] = min(ans[i], dp[i][j]);
    rep (j,0,K-1)
        rep (i,0,(1<<K)-1)
            if (!((i>>j)&1)) ans[i] = min(ans[i], ans[i|(1<<j)]);
    // rep (i,0,(1<<K)-1) cerr<<ans[i] <<' ';
    // cerr<<endl;
}

void Main(){
    scanf("%d%d%d", &n, &m, &K);
    cnt = 0;
    rep (i,1,n) head[i] = 0, V[i].clear();
    rep (i,1,m){
        int x, y, w; scanf("%d%d%d", &x, &y, &w);
        adde(x, y, w); adde(y, x, w);
    }
    scanf("%d%d", &E, &tmax);
    rep (i,1,E){
        int p, x, t;
        scanf("%d%d%d", &p, &x, &t);
        V[x].push_back(pii(p, t));
    }
    scanf("%d%d", &s1, &s2);
    solve(s1, dp1, ans1);
    solve(s2, dp2, ans2);
    ans = inf;
    rep (i,0,(1<<K)-1) ans = min(ans, max(ans1[i], ans2[((1<<K)-1)^i]));
    printf("%d\n", ans<=tmax?ans:-1);
    return;
}

int main(){
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

C. Magical Rearrangement

题意:告诉你一个数字里面有多少个 0 多少个 1 ... 多少个 9 ,让你构造出相邻数字不同并且没有前导零的字典序最小的数。无解输出 -1 。

做法:假设数总长为 n ,先特判单个 0 的情况,不要判成 -1 了。首先非零数字,如果有一个数字出现次数 $> \frac{n+1}{2}$ ,那么肯定无解;如果 0 出现次数 > $\frac{n}{2}$ ,那么也无解。其余肯定有解,考虑怎么构造。假设当前剩下长度为 len ,可以发现如果 len 为奇数,且如果有一个数字 d 出现次数 $= \frac{len+1}{2}$ ,那么必然是 d x d y d z d ... d 这样放置。所以当前位只能放 d 。其余情况都没有必须要放的数字,那么就找到最小的放上去就可以了。【吐槽:这题本来早就写好了...结果那个判单个 0 的那句话...写在了判 -1 的后面...我以为它判了 0 ...其实它判了 -1 ... 一直没找到错..我真的会栓q..】

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);i++)

using namespace std;

int n, a[100];

void Main(){
    int mx = 0; n = 0;
    rep (i,0,9){
        scanf("%d", &a[i]);
        mx = max(a[i], mx);
        n += a[i];
    }
    if (n==1&&a[0]>0){puts("0"); return;}
    if (mx>(n+1)/2 || a[0]>n/2){puts("-1"); return;}
    
    int last=-1;
    rep (i,1,n){
        int l = (n-i+1), st = i==1?1:0;
        if (l&1){
            bool flag=0;
            rep (j,st,9)
                if (j!=last && a[j]==((l+1)/2)){
                    printf("%d", j);
                    last=j;
                    a[j]--;
                    flag=1;
                    break;
                }
            if (!flag){
                rep (j,st,9)
                    if (j!=last && a[j]){
                        printf("%d", j);
                        last=j;
                        a[j]--;
                        break;
                    }
            }
        } else{
            rep (j,st,9)
                if (j!=last && a[j]){
                    printf("%d", j);
                    last=j;
                    a[j]--;
                    break;
                }
        }
    }
    puts("");
    return;
}

int main(){
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

D. Pattern Lock

题意:有一个 $n\times m$ 的点阵,现在你要把每个点用一笔画连起来(类似手机的图案密码),规定是相邻两个点之间的那条线上不能有别的点,以及相邻两条线要成锐角。构造方案。

做法:【真的好恶心啊这个构造题】应该有好多种方案,我说说我的(这个东西必须要画画才行)

首先如果 n 为偶数( m 为偶数是对应的),一定可以构造一种两行两行连的方案(如图)

但是这个有个问题是如果 m = 2 ,n 为 偶数时,按照上面的方法处理有个角度是 90 度,所以要特判一下。

直接这样就好了。

然后看 n 和 m 都是奇数的情况,我是这样做的先按照偶数做到剩下三行,然后三行这么构造。

.....依次下去。一定都是锐角。具体可以看代码。

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)

using namespace std;

int n, m, now;

int main(){
    scanf("%d%d", &n, &m);
    if (n==1 && m==1){puts("1 1"); return 0;}
    if (n==2){
        rep (j,1,m){
            printf("%d %d\n", 1, j);
            printf("%d %d\n", 2, j);
        }
        return 0;
    }
    if (m==2){
        rep (i,1,n){
            printf("%d %d\n", i, 1);
            printf("%d %d\n", i, 2);
        }
        return 0;
    }
    if (!(n&1)){
        now = 1;
        for (int i=1; i<=n; i+=2){
            if (now==1){
                printf("%d %d\n", i, 1);
                printf("%d %d\n", i+1, m);
                for (int j=m; j>=2; j--){
                    printf("%d %d\n", i, j);
                    printf("%d %d\n", i+1, j-1);
                }
                now = 0;
            } else{
                printf("%d %d\n", i, m);
                printf("%d %d\n", i+1, 1);
                rep (j,1,m-1){
                    printf("%d %d\n", i, j);
                    printf("%d %d\n", i+1, j+1);
                }
                now = 1;
            }
        }
        return 0;
    }
    if (!(m&1)){
        now = 1;
        for (int j=1; j<=m; j+=2){
            if (now==1){
                printf("%d %d\n", 1, j);
                printf("%d %d\n", n, j+1);
                for (int i=n; i>=2; i--){
                    printf("%d %d\n", i, j);
                    printf("%d %d\n", i-1, j+1);
                }
                now = 0;
            } else{
                printf("%d %d\n", n, j);
                printf("%d %d\n", 1, j+1);
                rep (i,1,n-1){
                    printf("%d %d\n", i, j);
                    printf("%d %d\n", i+1, j+1);
                }
                now = 1;
            }
        }
        return 0;
    }
    now = 1;
    for (int i=1; i<n-2; i+=2){
        if (now==1){
            printf("%d %d\n", i, 1);
            printf("%d %d\n", i+1, m);
            for (int j=m; j>=2; j--){
                printf("%d %d\n", i, j);
                printf("%d %d\n", i+1, j-1);
            }
            now = 0;
        } else{
            printf("%d %d\n", i, m);
            printf("%d %d\n", i+1, 1);
            rep (j,1,m-1){
                printf("%d %d\n", i, j);
                printf("%d %d\n", i+1, j+1);
            }
            now = 1;
        }
    }
    if (!now){
        printf("%d %d\n", n-2, m);
        printf("%d %d\n", n-1, 1);
        printf("%d %d\n", n, m);
        for (int j=m; j>1; j-=2){
            printf("%d %d\n", n-2, j-1);
            printf("%d %d\n", n-1, j);
            printf("%d %d\n", n-2, j-2);
            printf("%d %d\n", n, j-1);
            printf("%d %d\n", n-1, j-1);
            printf("%d %d\n", n, j-2);
        }
    } else{
        printf("%d %d\n", n-2, 1);
        printf("%d %d\n", n-1, m);
        printf("%d %d\n", n, 1);
        for (int j=1; j<m; j+=2){
            printf("%d %d\n", n-2, j+1);
            printf("%d %d\n", n-1, j);
            printf("%d %d\n", n-2, j+2);
            printf("%d %d\n", n, j+1);
            printf("%d %d\n", n-1, j+1);
            printf("%d %d\n", n, j+2);
        }
    }
    return 0;
}
View Code

【这题解写的我好累】

 

【中间几题由于不可做所以没有放进训练赛里面..】

 

H. Reverse the String

题意:给一个字符串,你可以反转一个区间,问得到字典序最小的字符串是什么。

做法:【一开始想用 sa ..板子打好了以后发现不能这么做...】首先翻转的区间一定是左端点比右端点大,且左端点最靠前,根据这个可以确定左端点 L 。现在只需要找到右端点。首先右端点的字符一定是 L 之后最小的,可以把这些 R 都拿出来。然后我们依次反转来比较字典序。毕竟字典序的方法就是求 lcp 然后比较 lcp+1位置的大小。lcp 比较常见的是二分+hash以及后缀数组,但是这里面有反转,所以没法用 sa ,只能二分加 hash ,hash 维护一个正向一个反向。具体见代码。

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define ll long long

using namespace std;

const int N = 1e5+10;
const int bas = 10007;
const int mod = 1e9+7;
int n, m, h1[N], h2[N], pw[N], q[N], flag, L, R, tot, ans;
char s[N], t[N];
set<int> S[500];

int get_h1(int l,int r){
    return (h1[r] + mod - (ll)h1[l-1]*pw[r-l+1]%mod)%mod;
}

int get_h2(int l,int r){
    l = m-l+1; r=m-r+1;
    swap(l, r);
    return (h2[r] + mod - (ll)h2[l-1]*pw[r-l+1]%mod)%mod;
}

int get_hash(int l,int p){
    if (l<=p){
        return get_h2(p-l+1, p);
    } else{
        return ((ll)get_h2(1, p)*pw[l-p]%mod+get_h1(p+1, l))%mod;
    }
}

char get_pos(int pos,int p){
    if (pos > p) return t[pos];
    else return t[p-pos+1];
}

bool cmp(int x, int y){
    // cerr<<get_hash(3, 2)<<' '<<get_hash(3, 5)<<endl;
    int l=1, r=m, mid;
    while (l<=r){
        mid = (l+r)>>1;
        if (get_hash(mid, x) == get_hash(mid, y)) l=mid+1;
        else r=mid-1;
    }
    int lcp = l-1;
    // cerr<<"lcp = "<<lcp<<endl;
    if (lcp == m) return 0;
    else return get_pos(lcp+1, x) < get_pos(lcp+1, y);
}

void Main(){
    scanf("%s", s+1); n=strlen(s+1);
    rep (i,0,25) S[i].clear();
    rep (i,1,n) S[s[i]-'a'].insert(i);
    tot = 0;
    L = 1, R = 1; tot=0;
    rep (i,1,n){
        S[s[i]-'a'].erase(i);
        flag=0;
        rep (j,0,s[i]-'a'-1)
            if (!S[j].empty()){
                for (auto x:S[j]) q[++tot] = x;
                flag = 1;
                break;
            }
        if (flag){L=i; break;}
    }
    if (!flag){
        rep (i,1,n) printf("%c", s[i]);
        puts("");
        return;
    }
    rep (i,1,L-1) printf("%c", s[i]);
    rep (i,L,n) t[i-L+1] = s[i];
    m = n-L+1;
    rep (i,1,m) h1[i] = ((ll)h1[i-1]*bas + t[i])%mod;
    reverse(t+1, t+1+m);
    rep (i,1,m) h2[i] = ((ll)h2[i-1]*bas + t[i])%mod;
    reverse(t+1, t+1+m);
    // cerr<<cmp(5, 2)<<endl;
    ans = 1;
    rep (i,1,tot){
        q[i] = q[i]-L+1;
        if (cmp(q[i], ans)) ans = q[i];
    }
    reverse(t+1, t+1+ans);
    rep (i,1,m) printf("%c", t[i]);
    puts("");
    return;
}

int main(){
    pw[0] = 1;
    rep (i,1,N-1) pw[i] = (ll)pw[i-1]*bas%mod;
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

I. Fake Walsh Transform

题意:有 $0, 1, 2, ... 2^m-1$ 这些数,给出一个 $< 2^m-1$ 的数 n ,问你最多可以用多少个数恰好异或出 n 。

做法:可以发现 $m > 1$ 时我们可以用 $0 .. 2^m-1$ 除掉 n 之外的数异或出 n ,肯定是最多的了,而 $n=0$ 时甚至不用除掉 n 。 $m = 1$ 要特判,$n=0$ 时要除掉 1 ,$n =1$ 时反而可以全选上。

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define ll long long

using namespace std;

ll n, m;

void Main(){
    scanf("%lld%lld", &m, &n);
    if (m==1){
        if (n==0) puts("1");
        else  puts("2");
        return;
    }
    if (!n) cout<<(1ll<<m)<<endl;
    else cout<<(1ll<<m)-1<<endl;
    return;
}

int main(){
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

J. Anti-merge

题意:给一个矩阵,每个格子里面有数字,相邻如果是相同数字可以合并。现在你可以给他们打上 tag ,使得只有相同数字相同 tag 才可以合并。你要使得所有格子都没法合并,问你最少用多少种颜色的 tag ,以及这种条件下,最少要给多少个格子打 tag 。

做法:不难发现,颜色最少只要一种肯定够了。对于同一个数字的连通块,一种颜色的话说明打 tag 都不能相邻,然后你给这个棋盘黑白染色,相当于要么全给白格子打 tag ,要么全黑,就看那种颜色格子数最少就好了。

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define pii pair<int,int>
#define fi first
#define se second

using namespace std;

const int N = 505;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
int n, m, vis[N][N], cnt0, cnt1, tot, a[N][N];
pii Q[N*N];
vector<pii> ans;

bool chk(int x,int y){
    return (1<=x&&x<=n) && (1<=y&&y<=m);
}

void dfs(int x,int y){
//    cerr<<x<<' '<<y<<endl;
    Q[++tot] = pii(x,y);
    vis[x][y]=1;
    if ((x+y)&1) cnt1++; else cnt0++;
    rep (i,0,3){
        int tx = x+dx[i], ty = y+dy[i];
        if (chk(tx, ty) && a[tx][ty] == a[x][y] && !vis[tx][ty]) dfs(tx, ty);
    }
}

int main(){
    scanf("%d%d", &n, &m);
    rep (i,1,n) rep (j,1,m) scanf("%d", &a[i][j]);
    rep (i,1,n) rep (j,1,m)
        if (!vis[i][j]){
            cnt0 = cnt1 = tot = 0;
            dfs(i,j);
            int op;
            if (cnt0<cnt1) op=0; else op=1;
            rep (k,1,tot)
                if (((Q[k].fi+Q[k].se)&1) == op) ans.push_back(Q[k]);
        }
    if ((int)ans.size()==0) puts("0 0");
    else{
        printf("1 %d\n", (int)ans.size());
        for (auto x:ans) printf("%d %d 1\n", x.fi, x.se);
    }
    return 0;
}
View Code

 

K. Longest Continuous 1

题意:有一个 s 串是0, 1, 2, 3... 的二进制拼起来的。然后现在问 s 串长为 k 的前缀里面最长连续的 1 有几个。

做法:找规律就好了。大概就是在每次 2 的幂次的位置答案会加一。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define ll long long

using namespace std;

int n;

void Main(){
    scanf("%d", &n);
    if (n==1){puts("0"); return;}
    else if (n==2){puts("1"); return;}
    ll x = 1, now = 2, i = 1;
    while (now<n){
        x = x*2ll;
        i ++;
        now = now+x*i;
    }
    printf("%lld\n", i);
    return;
}

int main(){
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

L. Tree Game

题意:有一棵树,有一些节点的数字已知,剩下的未知,你要把他们补全,补成一个排列。然后补完之后,你可以每次操作一个非叶节点,你可以把该节点和它的儿子上的值重新排列,问你可以把它排成任意 i 号节点的值为 $a_i$ 的方案数。

做法:对于最下面的叶节点和它的父亲来考虑,可以发现如果已知的数字中,出现了两个及以上叶节点编号以外的值,那么就无解。否则的话,如果有一个以外的值,这个值就应该放在父亲上;否则父亲的值暂时未知。我们统计这个父亲和儿子中 $a_i$ 为 0 的节点个数 cnt0 ,如果如果有一个以外的值,方案数就应该乘上 $cnt0!$ ,并且把这个值赋给父亲,否则,父亲的值未知,相当于父亲的值暂时为 0 ,方案数乘上 $(cnt0-1)!$ 。然后我们再接着向上统计。最后 $a_1$ 的值必须是 1 或者 0 不能是其他数。

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define ll long long

using namespace std;

const int N = 2e5+10;
const int mod = 998244353;
int n, a[N], cnt, head[N], vis[N], fac[N], ans, flag;
struct edge{
    int to, nxt;
}e[N];

void adde(int x,int y){
    e[++cnt].to=y; e[cnt].nxt=head[x]; head[x]=cnt;
}

void dfs(int u){
    if (!flag) return;
    for (int i=head[u]; i; i=e[i].nxt) dfs(e[i].to);
    if (!head[u]) return;
    for (int i=head[u]; i; i=e[i].nxt) vis[e[i].to] = 1;
    
    int cnt=0, cnt0=0;
    if (a[u] && !vis[a[u]]) cnt++;
    if (!a[u]) cnt0++;
    for (int i=head[u]; i; i=e[i].nxt){
        int v = e[i].to;
        if (a[v]!=0){
            if (!vis[a[v]]) cnt++;
        } else cnt0++;
    }
    if (cnt>1){flag=0; return;}
    if (cnt == 1){
        ans = (ll)ans*fac[cnt0]%mod;
        for (int i=head[u]; i; i=e[i].nxt){
            int v = e[i].to;
            if (a[v]&&!vis[a[v]]){a[u] = a[v]; break;}
        }
        if (a[u]&&!vis[a[u]]) a[u]=a[u];
    } else if (cnt0){
        a[u] = 0;
        ans = (ll)ans*fac[cnt0-1]%mod*cnt0%mod;
    }
    for (int i=head[u]; i; i=e[i].nxt) vis[e[i].to] = 0;
}

void Main(){
    scanf("%d", &n);
    cnt = 0;
    rep (i,1,n) head[i] = vis[i]=0;
    rep (i,2,n){
        int x; scanf("%d", &x);
        adde(x, i);
    }
    rep (i,1,n) scanf("%d", &a[i]);
    flag=1; ans = 1;
    dfs(1);
    if (a[1]&&a[1]!=1) puts("0");
    else{
        if (!flag) puts("0");
        else printf("%d\n", ans);
    }
    return;
}

int main(){
    fac[0]=1;
    rep (i,1,N-1) fac[i]=(ll)fac[i-1]*i%mod;
    int T; scanf("%d", &T);
    while (T--) Main();
    return 0;
}
View Code

 

【写完题解啦好有成就感 yeahyeah

posted @ 2022-07-28 00:40  bestfy  阅读(162)  评论(0编辑  收藏  举报