CF238题解

CF238

Codeforces Round 148 (Div. 1)

CF238A

link

CF238A题意

给出两个整数 \(n,m\),现在问你有多少个序列 \(a\) 满足:

  • 序列长度为 \(n\)
  • \(a_i\in[0,2^m-1]\)
  • \(\forall 1\le i\le j \le n,a_i\oplus a_{i+1}\oplus\dots\oplus a_j\neq0\)

答案对 \(10^9+9\) 取模。

CF238A题解

不难想到转化题意,即设 \(s_i=\oplus_{j=1}^ia_j\),那么题意转化为 \(\forall 0\le i< j \le n,s_i\oplus s_j\neq0\)
然后发现这个相当于在 \([1,2^m-1]\) 中选不同的 \(n\) 个数。
然后就没了。

CF238A代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10, mod = 1e9 + 9;
int n, m;
signed main(){
    n = read(); m = read();int ans = 1, pw = 1;
    for(int i = 1;i <= m;i++){pw = (pw * 2ll) % mod;}pw = (pw - 1 + mod) % mod;
    for(int i = 0;i < n;i++){ans = ans * (pw - i + mod) % mod;}
    printf("%lld\n",ans);
    return 0;
}

CF238B

link

CF238B题意

给出一个长度为 \(n\) 的数组 \(a(a_i\ge 0)\) 和一个数字 \(h\)
现在需要你将其划分成两个序列(可以有一个是空的),使得优美度最小。
定义一个函数 \(f(i,j)\) 表示,当 \(i,j\) 被划分为一个集合时,\(f(i,j)=a_i+a_j\),否则 \(f(i,j)=a_i+a_j+h\)
一个划分方式的优美度定义为 \(\max{f(i,j)}-\min{f(i,j)}\)
需要你给出方案。

CF238B题解

我们先将数组都归到第一个序列中,然后看哪些需要挪到第二个序列。
经过简单的思考,发现只有最小的那个数字所在的序列发生改变时,答案有可能改变。
然后比一下就OK了。

CF238B代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10;
int n, h;
struct node{
    int val, id;
    node(int val = 0,int id = 0):val(val),id(id){}
    friend bool operator < (node a,node b){return a.val < b.val;}
}a[maxn];
bool cmp(node a,node b){return a.id < b.id;}
signed main(){
    n = read();h = read();
    for(int i = 1;i <= n;i++)a[i] = node(read(),i);
    sort(a + 1,a + 1 + n);
    int val1 = max(h + a[n].val + a[1].val,a[n].val + a[n - 1].val) - min(a[1].val + a[2].val + h,a[2].val + a[3].val);
    int val2 = a[n].val + a[n - 1].val - a[1].val - a[2].val;
    if(val1 < val2){a[1].val = -1;}
    sort(a + 1,a + 1 + n,cmp);
    printf("%d\n",min(val1,val2));
    for(int i = 1;i <= n;i++){
        putchar((a[i].val == -1) ? '1' : '2');
        putchar(' ');
    }
    puts("");
    return 0;
}

CF238C

link

CF238C题意

给你一棵有 \(n\) 个节点的树,每条边有一个方向。问你最少经过多少次变换,使得整棵树没有入度的点不超过两个。
\(n\le 3000\)

CF238C题解

这里有一个 \(O(nk^2)(k=2)\) 的做法。
\(f_{u,i,j}\) 表示以 \(u\) 为根的子树中,有 \(i\) 个点(包括自己)没有入度,自己 (\(j=0/1\iff\) 有/没有) 入度的最少次数。
显然,对于 \(u\) 的儿子 \(v\),如果设 \(w\) 表示改变方向到 \(u\to v\) 的花费,有

\[f_{u,i + j,k_x}=\min{f_{u,i,k_x}+f_{v,j+k_y,k_y}+w}\\ f_{u,i + j,0}=\min{f_{u,i+k_x,k_x}+f_{v,j,k_y}+w} \]

没了。

CF238C代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 3e3 + 10;
int n;
int f[maxn][3][2];
vector<pair<int,int> > edg[maxn];
void dfs(int u,int fa){
    f[u][1][1] = 0;
    for(auto i : edg[u]){
        int v = i.first, w = i.second;
        if(v != fa){
            int g[3][2];memset(g,0x3f,sizeof(g));
            dfs(v, u);
            for(int i = 0;i < 3;i++)
            for(int j = 0;j < 3 - i;j++)
                for(int kx = 0;kx < 2;kx++)
                for(int ky = 0;ky < 2;ky++){
                    g[i + j][kx] = min(g[i + j][kx], w + f[u][i][kx] + f[v][j + ky][ky]);
                    g[i + j][0 ] = min(g[i + j][0 ],!w + f[u][i + kx][kx] + f[v][j][ky]);
                }
            memcpy(f[u],g,sizeof(g));
        }
    }
}
signed main(){
    n = read();int u, v;
    for(int i = 1;i < n;i++){
        u = read(); v = read();
        edg[u].push_back(make_pair(v, 0));
        edg[v].push_back(make_pair(u, 1));
    }
    memset(f,0x3f,sizeof(f));
    dfs(1, 0);int ans = 0x3f3f3f3f;
    for(int i = 0;i <= 2;i++)
        for(int j = 0;j < 2;j++)
            ans = min(ans,f[1][i][j]);
    printf("%d\n",ans);
    return 0;
}

CF238D

link

CF238D题意

有一种编程语言,在这种语言下一个程序可以用由数字、\(<\)\(>\) 组成的字串表示。程序运行时有一个指针,移动方向向右。

我们重复以下操作直到指针移动到字串外。

  • 如果指针指向一个数字,则输出这个数字并将数字减 \(1\)。如果输出的数字为 \(0\),则将它从字串中删除。
  • 如果指针指向 \(<\)\(>\),则改变移动方向,并以这个方向移动一格。若为 \(<\) 则移动方向向左,若为 \(>\) 则移动方向向右。若移动后指针仍然指向 \(<\)\(>\),则将移动前指向的字符删除。

对于给出的长度为 \(n\) 的字串,有 \(q\) 组询问,每次询问子串 \([l,r]\) 作为一段程序时,每个数字的输出次数为多少。

CF238D题解

完全想不到思路
发现可以 \(O(nd)(d=10)\) 的计算一个区间的贡献,但是显然会炸没的。
发现炸是因为重复计算太多东西了,考虑一次计算就算出所有答案。
在最左边放>防止越界。
然后定义 \(f_{0/1,i}\) 分别表示从 \(i\) 出发第一次准备向左移动时的答案和第一次准备到达 \(i\) 时的答案的前缀和。
因为一段区间结束是从最左端或者最右端出来,而从这个定义显然是时间更靠前的那个(时间更靠前的那个一定前缀和更小)是出来的前缀和。进来的前缀和显然。
那么答案就变成了 \(\min{(f_{1,r+1}-f_{0,l})}-f_{1,l}\)
值得注意的一点是,有的时候 \(x\) 已经被删除了,但是没有更新出来的答案。这个时候并查集做一下就好。

CF238D代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10;
int n, q;
char ch[maxn];
int len, l[maxn], r[maxn];
bool book[2][maxn];
int f[2][maxn][10];

int fa[maxn];
int getf(int x){return fa[x] == x ? x : fa[x] = getf(fa[x]);}
void merg(int x,int y){int fx = getf(x), fy = getf(y);if(fx != fy)fa[fy] = fx;}

int pos, opt, buc[10];
void del(int x){r[l[x]] = r[x];l[r[x]] = l[x];len--;}
void move(){
    int nxt = 0;
    if(isdigit(ch[pos])){
        buc[ch[pos] - '0']++;
        if(ch[pos] == '0')del(pos);
        else ch[pos]--;
        nxt = opt ? r[pos] : l[pos];
    }
    else{
        if(ch[pos] == '<')opt = 0;
        else opt = 1;
        nxt = opt ? r[pos] : l[pos];
        if(pos && !isdigit(ch[nxt]))del(pos);
    }
    if(opt == 1 && !book[1][nxt]){
        for(int i = 0;i < 10;i++)f[1][nxt][i] = buc[i];
        book[1][nxt] = 1;
    }
    if(opt == 0){
        int j = getf(nxt) + 1;
        while(j <= pos){
            book[0][j] = 1;
            for(int i = 0;i < 10;i++)f[0][j][i] = buc[i];
            merg(j,j - 1);j = getf(j) + 1;
        }
    }
    pos = nxt; return;
}

signed main(){
    n = read(); q = read();
    scanf("%s",ch + 1);ch[0] = '>';
    for(int i = 1;i <= n;i++){fa[i] = i;r[i] = i + 1;l[i] = i - 1;}
    memset(f,0x3f,sizeof(f));
    r[0] = 1;opt = 1;pos = 0;len = n;
    while(len > 0 && pos <= n){move();}
    for(int j = 1;j <= q;j++){
        int l = read(), r = read();
        for(int i = 0;i < 10;i++)
            printf("%d ",(book[0][l] ? min(f[0][l][i],f[1][r + 1][i]) : f[1][r + 1][i]) - f[1][l][i]);
        puts("");
    }
    return 0;
}

CF238E

link

CF238E题意

给出一张 \(n\) 个点 \(m\) 条边的有向图。每条边的长度相同(都是一)。Urpal想从 \(a\) 坐车到 \(b\)。有 \(k\) 条路线,每条路线都会在每一秒从 \(s_i\) 发出一辆公交车,随机选择一条最短路到 \(t_i\)。如果不能从 \(s_i\to t_i\),那么这条路线就不会发车。只要一辆车经过Urpal,他就可以上车。他可以在这辆车行进到任意位置时下车。
现在Urpal想知道,他能否在有限的时间内到达目的地,以及在最坏的情况下,最少换乘次数。
任意时刻,Urpal只知道自己的位置和想去的位置。当他上车之后他只知道这辆车是哪辆车。当然,他知道整张图和每个路线的起止地点。
但是Urpal并不知道这辆车的具体路线。
告诉他最少换乘次数,或者告诉他最坏情况下,他不可能在有限时间到达终点。

CF238E题解

首先发现两个性质:

  • 如果 \(a\)(或 \(b\))不在一条路线 \(i\) 的必经之路上,那么这个路线就一定不会经过这个点。
  • 如果 \(x\) 为你现在所在的点,\(i\) 是你乘坐的路线,那么这个路线一定给你送到最劣的位置。

然后发现你的状态可以设计成 \((x,i)\) 表示你在 \(x\) 位置,现在乘坐的是第 \(i\) 条线路。
然后设 \(f_{x,i}\) 表示状态是 \((x,i)\) 时到达终点的最小代价,\(x\to y\) 表示存在一条从 \(x\)\(y\) 的边。
发现转移只有两种:

\[f_{x,i}=\min(\max_{x\to y,i线路可能经过y}f_{y,i},\min_{j线路一定经过x}f_{x,j}+1) \]

至于怎么判断可能经过和一定经过,你可以先跑一个全源最短路,然后对于 \(x\to y\) 的路径,如果需要判断 \(z\) 是否可能被经过,那么当 \(e_{x,z}+e_{z,y}=e_{x,y}\) 的时候,这个点就是可能经过的。
那一定经过呢?只需要判断从 \(x\) 出发的、长度为 \(e_{x,z}\) 的,且可能在最短路上的 \(z\) 的数量,如果只有一个那么就是一定经过。
然后从终点反向递推到起点即可。

CF238E代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 100 + 10;
int n, m, S, T;
int e[maxn][maxn];
int K, st[maxn], ed[maxn];
bool avail[maxn];
int tot[maxn][maxn];
int deg[maxn][maxn];
int f[maxn][maxn];
signed main(){
    n = read(); m = read(); S = read(); T = read();
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++)
            e[i][j] = (i != j) * (0x3f3f3f3f);
    for(int i = 1;i <= m;i++){int u = read(), v = read();e[u][v] = 1;}

    for(int k = 1;k <= n;k++)
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= n;j++)
                e[i][j] = min(e[i][j],e[i][k] + e[k][j]);

    K = read();
    for(int i = 1;i <= K;i++){st[i] = read();ed[i] = read();avail[i] = e[st[i]][ed[i]] != 0x3f3f3f3f;}
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= K;j++){
            if(!avail[j] || e[st[j]][i] + e[i][ed[j]] != e[st[j]][ed[j]])continue;
            tot[j][e[st[j]][i]]++;
            for(int k = 1;k <= n;k++)
                if(e[i][k] == 1 && e[st[j]][i] + e[i][k] + e[k][ed[j]] == e[st[j]][ed[j]])
                    deg[i][j]++;
        }
    memset(f,0x3f,sizeof(f));
    for(int i = 1;i <= K;i++){
        if(avail[i] && e[st[i]][T] + e[T][ed[i]] == e[st[i]][ed[i]])
            f[T][i] = 0;
    }
    for(int k = 0;k < n;k++){
        queue<pair<int,int> > que;
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= K;j++)
                if(f[i][j] == k){que.push(make_pair(i, j));}
        while(!que.empty()){
            int x = que.front().first, i = que.front().second;que.pop();
            if(tot[i][e[st[i]][x]] == 1){
                if(x == S){printf("%d\n",f[x][i] + 1);return 0;}
                for(int j = 1;j <= K;j++)
                    if(avail[j] && e[st[j]][x] + e[x][ed[j]] == e[st[j]][ed[j]])
                        f[x][j] = min(f[x][j],f[x][i] + 1);
            }
            for(int y = 1;y <= n;y++){
                if(e[y][x] == 1 && e[st[i]][y] + e[y][x] + e[x][ed[i]] == e[st[i]][ed[i]]){
                    if(!--deg[y][i] && f[y][i] > f[x][i]){
                        f[y][i] = f[x][i];
                        que.push(make_pair(y,i));
                    }
                }
            }
        }
    }
    puts("-1");
    return 0;
}
posted @ 2023-12-26 07:46  Call_me_Eric  阅读(46)  评论(0)    收藏  举报
Live2D