IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(济南)题解 -> 正文阅读

[数据结构与算法]第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(济南)题解

A.Matrix Equation

  • 题意
    给定 n × n n\times n n×n 01 01 01矩阵 A , B A,B A,B,求满足 A × C = B ? C A\times C=B\cdot C A×C=B?C的矩阵个数。

  • 解题思路
    由于是矩阵乘法,所以对于 C C C的每一列都可以单独考虑,即可以设置C的每一列一开始都有 n n n个未知数,那么自然可以得到 n n n条异或方程组,然后利用高斯消元求解出自由变元个数 x x x,这些是可以自由取值的,所以矩阵个数则为 2 x 2^{x} 2x

  • AC代码

#include<bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 2e2 + 10, P = 998244353;
int n, A[N][N], B[N][N];
int a[N][N], free_num;
int equ, var;
int pow2[N];
void cal(int n){
    pow2[0] = 1;
    for(int i = 1; i <= n; ++ i){
        pow2[i] = 1LL * pow2[i - 1] * 2 % P;
    }
}
void init(){
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < n; ++ j){
            a[i][j] = A[i][j];
        }
    }
}
int Gauss(){
    int max_r, col, k;
    for(k = 0, col = 0; k < equ && col < var; ++ k, ++ col){
        max_r = k;
        for(int i = k + 1; i < equ; ++ i){
            if(abs(a[i][col]) > abs(a[max_r][col])){
                max_r = i;
            }
        }
        if(!a[max_r][col]){
            -- k;
            continue;
        }
        if(max_r != k){
            for(int j = k; j < var + 1; ++ j){
                swap(a[k][j], a[max_r][j]);
            }
        }
        for(int i = k + 1; i < equ; ++ i){
            if(a[i][col]){
                for(int j = col; j < var + 1; ++ j){
                    a[i][j] ^= a[k][j];
                }
            }
        }
    }
    for(int i = k; i < equ; ++ i){
        if(a[i][col]){
            return - 1;
        }
    }
    if(k < var)return var - k;//自由变元个数。
    return 0;
}
int main(){
    scanf("%d", &n);
    cal(n);
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < n; ++ j){
            scanf("%d", &A[i][j]);
        }
    }
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < n; ++ j){
            scanf("%d", &B[i][j]);
        }
    }
    equ = var = n;
    ll res = 1;
    for(int j = 0; j < n; ++ j){
        //枚举第一行乘c的解。
        init();
        for(int i = 0; i < n; ++ i){
            a[i][i] = (A[i][i] - B[i][j] + 2) % 2;
        }
        free_num = Gauss();
        if(free_num != -1){
            res = res * pow2[free_num] % P;
        }
    }
    printf("%lld\n", res);
    return 0;
}

C. Stone Game

  • 题意
    a 1 a_1 a1?个一颗石头的堆, a 2 a_2 a2?个两颗石头的堆,有 a 3 a_3 a3?个三颗石头的堆。你可以将两个堆进行合并,合并后石头数量为两堆石头数量之和 ( x + y ) (x+y) (x+y),代价为 ( x m o d ?? 3 ) ( y m o d ?? 3 ) (x\mod 3)(y\mod 3) (xmod3)(ymod3)。你需要将它们合并最后变成一个堆,问最小代价总和。

  • 解题思路
    我们注意到,用 x m o d ?? 3 = 0 x\mod 3=0 xmod3=0的堆去合并,是不需要任何代价的,所以实际上我们可以不用考虑 x m o d ?? 3 = 0 x\mod 3=0 xmod3=0的堆。那么考虑其他两个,我们发现,用余数为 1 1 1和余数为 2 2 2的进行合并是最优的,因为代价少,且可以合成一个不用考虑的堆。
    所以我们的策略就是先抵消掉 m i n ( a 1 , a 2 ) min(a_1,a_2) min(a1?,a2?),然后对剩下那个不为 0 0 0的堆进行消耗,也就是只剩下余数为 1 1 1或者余数为 2 2 2的堆,这个时候我们发现 3 3 3 3 3 3个一起会最优,因为可以凑出一个 3 3 3,且代价最小。故据此处理题解。

  • AC代码

#include<bits/stdc++.h>

using namespace std;
using ll = long long;
int a[3];
int main(){
    for(int i = 0; i < 3; ++ i)cin >> a[i];
    ll ans = 0;
    int x = min(a[0], a[1]);
    a[0] -= x, a[1] -= x;
    a[2] += x;
    ans += 2 * x;
    x = a[0] / 3;
    a[0] %= 3;
    ans += 3 * x;
    x = a[1] / 3;
    a[1] %= 3;
    ans += 6 * x;
    if(a[0] == 2)ans += 1;
    if(a[1] == 2)ans += 4;
    cout << ans << endl;
    return 0;
}

D. Fight against involution

  • 题意
    n n n个学生,每个学生论文字数为 w i ∈ [ L i , R i ] w_i\in [L_i,R_i] wi?[Li?,Ri?],若有 k i k_i ki?个学生 j ∈ [ 1 , n ] j\in [1,n] j[1,n]满足 w j > w i w_j>w_i wj?>wi?,那么学生 i i i的分数为 n ? k i n-k_i n?ki?。最开始所有学生写了 R i R_i Ri?个字,现在你可以在每个学生分数不减小的情况下调整每个学生的论文字数,问最小的字数和是多少。

  • 解题思路
    由题意得,上限 R R R实际上就决定了每个学生的分数,所以我们只要控制原先大于他的 w i w_i wi?不变,即可。所以我们可以将 R R R升序排列,那么同一个 R R R的肯定是取最大的 L L L(因为得分一样,取最大的 L L L是为了让他们都能取到相同的值),但这个值同样需要大于等于前面 R R R选定的值。
    据此,我们实际上就可以用 l a s t last last变量来记录上一轮选定的字数,然后双指针滑过去取每一段 R R R相同的区间即可。

  • AC代码

#include<bits/stdc++.h>
#define L second 
#define R first

using namespace std;
using pii = pair<int, int>;
using ll = long long;

const int N = 1e5 + 10;

int n;
pii a[N];
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d%d", &a[i].L, &a[i].R);
    }
    sort(a + 1, a + 1 + n);
    ll res = 0;
    int last = 0;
    for(int l = 1, r; l <= n; l = r){
        r = l;
        while(r <= n && a[r].R == a[l].R)++ r;
        //贪心取最左端。
        int tmp = max(last, a[r - 1].L);
        res += 1LL * (r - l) * tmp;
        last = tmp;
    }
    printf("%lld\n", res);
    return 0;
}

G.Xor Transformation

  • 题意
    给定 X , Y X,Y X,Y,其中 Y < X Y<X Y<X。现在你可以进行不超过 5 5 5次的操作,每次操作可以选取值 A , A ∈ [ 0 , X ) A,A\in [0,X) A,A[0,X),然后使 X = X ? x o r A X=X\ xor A X=X?xorA。问你是否可以将 X X X变成 Y Y Y

  • 解题思路
    如果我们直接让 X ? x o r ? A = Y X \ xor\ A=Y X?xor?A=Y,那么 A = X ? x o r ? Y A=X\ xor\ Y A=X?xor?Y,那么如果 A ∈ [ 0 , X ) A\in [0,X) A[0,X),那么直接一步操作即可。否则,说明 X ? x o r ? Y > X X\ xor\ Y>X X?xor?Y>X,而我们知道异或两次相同的值,那么值是不变的,所以我们可以让 X = X ? x o r ? Y X=X \ xor \ Y X=X?xor?Y,那么再异或一次 X X X即可。

  • AC代码

#include<bits/stdc++.h>

using namespace std;
using ll = long long;
ll x, y;
int main(){
    cin >> x >> y;
    if((x ^ y) > x){
        puts("2");
        printf("%lld %lld", y, x);   
    }
    else{
        puts("1");
        printf("%lld\n", x ^ y);
    }
    return 0;
}

J.Tree Constructer

  • 题意
    有一颗 n n n个结点的数,需要你构造结点的权值 a i a_i ai?,使得边 ( x , y ) (x,y) (x,y)存在当且仅当 a x ∣ a y = 2 60 ? 1 a_x| a_y=2^{60}-1 ax?ay?=260?1

  • 解题思路
    我们可以先利用染色法构造二分图,将其分成左部和右部,为了让这两部分内的顶点互不影响,所以我们可以让左部顶点空出第一位,右部顶点空出第二位,这样可以保证左部顶点与左部顶点、右部顶点与右部顶点没有边。然后对于左部顶点与右部顶点的关系,针对每个左部顶点,我们可以拿出一位,然后让右部顶点中与该点没有边的点该位都为 0 0 0,这样能够保证题目要求。由于我们是要在左部顶点上拿位,所以我们要使得左部顶点数量尽可能少,所以如果左部比右部多,交换一下即可。具体看 A C AC AC代码。

  • AC代码

#include<bits/stdc++.h>

using namespace std;
using ll = long long;

int n;
bool g[110][110];
vector<int> p[2];//二分图。
int color[110];//color[u]表示u所染的颜色
void dfs(int u, int fu, bool op){
    p[op].push_back(u);
    color[u] = op;
    for(int v = 1; v <= n; ++ v){
        if(v == fu)continue;
        if(g[u][v])dfs(v, u, op ^ 1);
    }
}
void solve(){
    dfs(1, -1, 0);
    //因为我们是通过p[0]来构造p[1]。
    if(p[0].size() > p[1].size())swap(p[0], p[1]);
    vector<ll> res(n + 1, (1LL << 60) - 1);
    for(auto u : p[0])res[u] ^= 1;
    for(auto v : p[1])res[v] ^= 2;
    ll x = 4;
    for(auto u : p[0]){
        res[u] ^= x;
        for(auto v : p[1]){
            //将没有联系的且处于不同部的都清除这个点。
            if(!g[u][v] && color[u] != color[v])res[v] ^= x;
        }
        x *= 2;
    }
    for(int i = 1; i <= n; ++ i){
        printf("%lld ", res[i]);
    }
    puts("");
}
int main(){
    scanf("%d", &n);
    for(int i = 1, u, v; i < n; ++ i){
        scanf("%d%d", &u, &v);
        g[u][v] = g[v][u] = true;
    }
    solve();
    return 0;
}

L.Bit Sequence

  • 题意
    定义 f ( x ) f(x) f(x)为二进制下1的数量,现在给定长度为m的01序列a,你需要找出有多少 x ( x ∈ [ 0 , L ] ) x(x\in [0, L]) x(x[0,L])满足对于所有 a i a_i ai?,都满足 f ( x + i ) m o d ?? 2 = a i f(x + i) \mod 2 = a_i f(x+i)mod2=ai?

  • 解题思路
    由于 m m m特别小,其只会影响后面的七位,而对从第八位开始的位也只有进位时才会影响,所以我们可以暴力处理后七位的情况,然后我们可以用 c n t cnt cnt来记录从第八位开始的连续 1 1 1的个数,同时,用 s u m sum sum表示从 x x x第八位开始的数位之和,用 f ( i , j ) f(i,j) f(i,j)后面的七位数和 i i i进位相加的值二进制下 1 1 1的个数。所以,如果进位,则 f ( x + i ) = s u m ? c n t + f ( i + j ) f(x+i)=sum-cnt+f(i+j) f(x+i)=sum?cnt+f(i+j)(因为进位的 1 1 1被抵消了,我们这里多算了cnt);如果不进位的话,则 f ( x + i ) = s u m + f ( i + j ) f(x+i)=sum+f(i+j) f(x+i)=sum+f(i+j)。针对 f ( i + j ) f(i+j) f(i+j),由于其不会超过 256 256 256,所以我们可以预处理出 256 256 256一下的函数值。然后,同时由于是要对 2 2 2取余,所以我们实际上只在乎其奇偶性,同时由于加减法我们可以用异或运算实现,所以我们可以统一按异或处理。
    经过以上分析,且此题就是数位 d p dp dp的风格,所以我们可以从高位到低位依次考虑,用 d p [ p o s ] [ s u m ] [ c n t ] [ l i m i t ] dp[pos][sum][cnt][limit] dp[pos][sum][cnt][limit]来表示当前处理位为 p o s pos pos且数位和奇偶性为 s u m sum sum,连续 1 1 1个数奇偶性为 c n t cnt cnt l i m i t limit limit表示该位是否限定了上界的方案数。
    故我们需要开 d p [ 65 ] [ 2 ] [ 2 ] [ 2 ] dp[65][2][2][2] dp[65][2][2][2],由于状态数经过奇偶性处理后实际上非常少了,为了降低时间复杂度,减少不必要的搜索,我们可以使用记忆化搜索。
    具体见 A C AC AC代码。

  • AC代码

#include<bits/stdc++.h>

using namespace std;
using ll = long long;

int t, m, a[110], digit[65];
int f[300];//提前计算好256前的数位和奇偶性。
ll L, dp[65][2][2][2];
void init(){
    for(int i = 1; i <= 256; ++ i){
        f[i] = (f[i >> 1] + (i & 1)) % 2;
    }
}
ll cal(bool sum, bool cnt, bool limit){
    int up = limit ? L % 128 : 127;
    ll ans = 0;
    for(int i = 0; i <= up; ++ i){
        bool flag = true;
        for(int j = 0; j < m && flag; ++ j){
            if(i + j < 128){
                //说明没有进位。
                flag = (f[i + j] ^ sum) == a[j];
            }
            else{
                //说明存在进位,此时需要将多余的cnt给删掉。
                flag = (f[i + j] ^ cnt ^ sum) == a[j];
            }
        }
        ans += flag;
    }
    return ans;
}
ll dfs(int pos, bool sum, bool cnt, bool limit){
    //pos为当前正在处理的为,sum为当前数位和奇偶性,cnt为从第八位开始连续1的个数的奇偶性,limit为是否限定上界。
    ll &x = dp[pos][sum][cnt][limit];
    //cout << pos << " " << sum << " " << cnt << " " << limit << endl;
    if(~x)return x;
    if(pos <= 6){
        return x = cal(sum, cnt, limit);
    }
    int up = limit ? digit[pos] : 1;
    ll ans = 0;
    for(int i = 0; i <= up; ++ i){
        int one = cnt;
        if(i)one ^= 1;
        else one = 0;
        ans += dfs(pos - 1, sum ^ i, one, limit && i == up);
    }
    return x = ans;
}
void solve(){
    int len = 0;
    memset(dp, -1, sizeof(dp));
    for(int i = 63; i >= 0; -- i){
        digit[i] = (L >> i) & 1;
        if(digit[i]){
            len = max(len, i);
        }
    }
    printf("%lld\n", dfs(len, 0, 0, 1));
}
int main(){
    scanf("%d", &t);
    init();
    while(t -- ){
        scanf("%d%lld", &m, &L);
        for(int i = 0; i < m; ++ i){
            scanf("%d", &a[i]);
        }
        solve();
    }
    return 0;
}

M.Cook Pancakes!

  • 题意
    n n n个煎饼, k k k个锅,每个煎饼需要煎两面才可以熟,煎一面需要 1 s 1s 1s,问将煎饼全煎熟最小需要多少分钟。

  • 解题思路
    经典小学数学问题,即是相当于有 2 n 2n 2n个数: 1... n , ? 1.... ? n 1...n,-1....-n 1...n,?1....?n。每次可以删掉 k k k个数,其中 x , ? x x,-x x,?x不能一起删,问最少几次删完,按照上述,顺序删过去即可,答案则是 ? 2 n k ? \lceil\frac{2n}{k}\rceil ?k2n??。注意 k > n k>n k>n两次就可以删除。

  • AC代码

#include<bits/stdc++.h>

using namespace std;

int n, k;
int main(){
    cin >> n >> k;
    if(k > n){
        cout << 2 << endl; 
    }
    else{
        cout << (2 * n + k - 1) / k << endl;
    }
    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 12:01:22  更:2021-10-08 12:04:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 11:51:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码