icpc2017南宁

icpc2017南宁

A. Abiyoyo

题意: 签到水题

**题解: ** 签到水题

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int main(){
    int t,k;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&k);
        for(int i=0;i<k;i++){
            printf("Abiyoyo, Abiyoyo.\n");
        }
        printf("Abiyoyo, yo yoyo yo yoyo.\n");
        printf("Abiyoyo, yo yoyo yo yoyo.\n");
    }
    return 0;
}

I. Rake It In

**题意: ** 给定一个4 * 4的方阵,a和b博弈,每次选择2 * 2的方阵,得到方阵内的数字和,然后将方阵按照逆时针旋转,之后轮到下一个人操作。如果当前是奇数次,那么要让总的和最大,为偶数次,那么要让总的和最小。问最后答案是多少。

题解: 这种规模不是很大的博弈论问题都是通过搜索来完成,可以旋转2 * 2方阵只有9种情况,因此可以搜索时枚举这9种情况,然后如果当前是奇数次,那么取最大值;如果是偶数次,那么取最小值。这种类型的题目之前在其他的区域赛中也有考过,解法类似。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;

int n, T, m, a[5][5];

int fun(int x, int y) {
    return a[x][y] + a[x + 1][y] + a[x][y + 1] + a[x + 1][y + 1];
}

void change(int x, int y) {
    int t = a[x][y];
    a[x][y] = a[x][y + 1];
    a[x][y + 1] = a[x + 1][y + 1];
    a[x + 1][y + 1] = a[x + 1][y];
    a[x + 1][y] = t;
}

void rechange(int x, int y) {
    int t = a[x][y];
    a[x][y] = a[x + 1][y];
    a[x + 1][y] = a[x + 1][y + 1];
    a[x + 1][y + 1] = a[x][y + 1];
    a[x][y + 1] = t;
}

int dfs(int k) {
    if (k == 2 * n) {
        int minv = 1e9 + 10;
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                minv = min(minv, fun(i, j));
            }
        }
        return minv;
    }

    int minv = 1e9 + 10, maxv = -1;
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            int t = fun(i, j);
            change(i, j);
            int tmp = dfs(k + 1);
            maxv = max(maxv, tmp + t);
            minv = min(minv, tmp + t);
            rechange(i, j);
        }
    }
    return (k & 1)? maxv: minv;
}

int main() {
    cin >> T;
    while(T--) {
        cin >> n;
        for (int i = 0; i < 4; ++i) 
            for (int j = 0; j < 4; ++j)
                cin >> a[i][j];
        cout << dfs(1) << endl;
    }
    return 0;
}

F. The Chosen One

题意: 给定1~n这n个数字,每次删除奇数位上的数字,问最后剩下的数字是多少. 1 < = n < = 1 0 50 1<=n<=10^{50} 1<=n<=1050

题解: 规律题。找规律可以看出,最后剩下的数字是小于等于n的最大的2的幂次方。因此java使用大数来写。

代码:

import java.io.*;
import java.math.*;
import java.util.*;
import java.text.*;

public class Main {
    public static void main(String[] args) {
        Scanner cin = new Scanner (new BufferedInputStream(System.in));
        int T = cin.nextInt();
        while((T--) != 0) {
            BigInteger n = cin.nextBigInteger();
            BigInteger tmp = new BigInteger("2");
            System.out.println(tmp = tmp.pow(n.bitLength() - 1));
        }
    }
}

H. The Game of Life

题意: 本题是元胞自动机游戏,当前有一个网格,对于网格内的每一个细胞来说,如果其周围(8个方向)活细胞数目小于2或者大于3,那么这个细胞死亡,如果其周围活细胞数目为2,那么这个细胞保持原来的状态;如果周围活细胞数目为3,那么细胞变为存活状态。问细胞繁衍321代后,还剩下多少个活细胞。在繁衍321代的过程中,活细胞数目最多的是哪一代,活细胞数目为多少。

题解: 由于本题的数据规模很小,因此直接模拟321代即可。模拟的话,需要维护一个数组point记录当前所有的活细胞,维护数组tmp记录当前所有活细胞周围的细胞。一个细胞在tmp数组中出现多少次,就表明其周围存活的细胞有多少个。借助这个条件,可以判断一个细胞在当前这代后是否还能存活。同时,如果一个细胞不出现在tmp数组,那么这个细胞必然死亡。然后按照这个思路,去模拟即可。

模拟题一直是本人的弱项,每次看到模拟题都是很抗拒,队里面也没有人写模拟题。以后只能我硬着头皮去好好练模拟题了。加油,攻克模拟题。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 1500;
typedef long long LL;
typedef pair<int, int> PII;

int n, T, m;
char mp[N][N];
PII point[N * N], tmp[N * N * 8];
int cnt, tot, Bounds = 500;
int dx[] = {1, 0, -1, 0, 1, 1, -1, -1}, dy[] = {0, 1, 0, -1, 1, -1, 1, -1};
int live[N][N];

void generation() {
    int res = 0;
    // 总的数目为tot:0~tot-1
    for (int i = 0, j = 0; i < tot; ) {
        while(j < tot && tmp[i] == tmp[j]) j++;
        int cur_cnt = j - i;
        if (cur_cnt < 2) {
            live[Bounds + tmp[i].first][Bounds + tmp[i].second] = 0;
        }
        else if (cur_cnt == 2) {
            if (live[Bounds + tmp[i].first][Bounds + tmp[i].second]) {
                point[res++] = tmp[i];
            }
        }
        else if (cur_cnt == 3) {
            live[Bounds + tmp[i].first][Bounds + tmp[i].second] = 1;
            point[res++] = tmp[i];
        }
        else {
            live[Bounds + tmp[i].first][Bounds + tmp[i].second] = 0;
        }
        i = j;
    }
    cnt = res;
}

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        memset(live, 0, sizeof live);
        cin >> n >> m;
        cnt = 0;
        for (int i = 0; i < n; ++i) {
            scanf("%s", mp[i]);
            for (int j = 0; j < m; ++j) 
                if (mp[i][j] == '#') {
                    point[cnt++] = {i, j};
                    live[Bounds+ i][Bounds + j] = 1;
                }
        }

        int a = 0, b = cnt;
        
        for (int ger = 1; ger <= 321; ++ger) {
            tot = 0;
            for (int i = 0; i < cnt; ++i) {
                for (int j = 0; j < 8; ++j) {
                    PII cur = {point[i].first + dx[j], point[i].second + dy[j]};
                    tmp[tot++] = cur;
                }
            }
            sort(tmp, tmp + tot);
            for (int i = 0; i < cnt; ++i) {
                if (*lower_bound(tmp, tmp + tot, point[i]) != point[i]) {
                    live[Bounds + point[i].first][Bounds + point[i].second] = 0;
                }
            }
            
            generation();
            if (cnt > b) {
                a = ger;
                b = cnt;
            }
            if (!cnt) break;
        }
        cout << a << " " << b << " " << cnt << endl;
    }
    return 0;
}

J. Rearrangement

题意: 给定一个2*n的矩阵,问是否能够通过调换矩阵内元素的位置使得任意相邻位置的两个元素和不是3的倍数,如果能的话输出yes,否则输出no。

题解: 由于可以调换任意位置,因此元素初始位置没有用。本题要求相邻元素和为3的倍数,因此可以把所有元素模上3,基本这种题都这么处理。那么所有的元素就只剩下0、1、2,那么只有0和0相邻、1和2相邻会出现3的倍数。因此可以把1和2分别放在各一边,然后使用0来分割。那么考虑0的摆放位置,核心就是0怎么放。

一般这种题目都是分类讨论,讨论0的数目,从0开始讨论。

这里记0、1、2的数目分别为cnt(0)、cnt(1)、cnt(2)。

我们知道一旦cnt(0)>n,那么无论怎么摆放,都会产生3的倍数,因此cnt(0)的上限为n。

那么我们分类讨论:

当cnt(0)==0,那么无论怎么摆放1、2都会相邻,产生3的倍数,但这需要出现1和2

当cnt(0)==1,那么无论怎么摆放1、2都会相邻,但这也需要出现1和2,这个情况可以归类为第一种

当cnt(0)==2,那么cnt(1)和cnt(2)一旦其中一个为偶数,另一个也为偶数,如果是偶数,那么0无论怎么放都会使得1、2相邻

当cnt(0)>=3&&cnt(0)<=n时,无论cnt(1)和cnt(2)多少,都可以将2个0作为挡板,其他的0嵌入到1或者2的堆里,这样就不会产生3

当cnt(0)>n,那么无论0怎么放,0和0都会相邻,产生3的倍数,编写代码即可

按照如山如上的分析,我们可以知道这种题目其实也是一种套路题,从一开始的3的倍数,就有模3的特定处理。对于相邻的问题,就行分块找挡板;对于0数目的讨论,一般都是找上下界,然后逐个分析讨论。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;

int n, T, m, a[N];

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        scanf("%d", &n);
        int cnt0 = 0, cnt1 = 0, cnt2 = 0;
        for (int i = 1;  i <= n  * 2; ++i) {
            scanf("%d", &a[i]);
            int t = a[i] % 3;
            if (t == 0) cnt0++;
            else if (t == 1) cnt1 ++;
            else if (t == 2) cnt2 ++;
        }
        int flg = 1;
        if (cnt0 <= 1 && cnt1 && cnt2) flg = 0;
        if (cnt0 == 2 && (cnt1 % 2 == 0)) flg = 0;
        if (cnt0 >= n + 1) flg = 0;
        if (flg) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

// 0 0 0 1 1 1

L. Twice Equation

题意: 每个测试样例给定一个L,要求找到一个大于等于L的n,满足存在m,使得 2 m ( m + 1 ) = n ( n + 1 ) 2m(m+1) = n(n+1) 2m(m+1)=n(n+1)成立。 1 < = L < = 1 0 190 1<=L<=10^{190} 1<=L<=10190

题解: 分析本题可以看出,满足条件的n值非常稀疏,通过打表可以看出,n的值为:0 3 20 119 696 4059 23660 137903 803760 4684659 27304196 159140519。从上面n的式子可以看出,n满足线性递推式子的性质,使用暴力枚举线性递推的系数,系数从-100枚举到100,暴力代码如下:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;

int n, T, m;

int main() {
    for (int a = -100; a <= 100; ++a) {
        for (int b = -100; b <= 100; ++b) {
            for (int c = -100; c <=100; ++c) {
                for (int d = -100; d <= 100; ++d) {
                // 0 3 20 119 696 4059 23660 137903 803760 4684659 27304196 159140519
                // f[i] = a * f[i - 3] + b * f[i - 2] + c * f[i - 1] + d
                    if (696 == 3 * a + 20 * b + 119 * c + d && 119 == 3 * b + 20 * c + d && 4059 == 20 * a + 119 * b + c * 696 + d && 23660 == 4059 * c + 696 * b + 119 * a + d) {

                        if (137903 == 696 * a + 4059 * b + 23660 * c + d)
                            if (803760 == 4059 * a + 23660 * b + 137903 * c + d)
                                if (4684659 == 803760 * c + 137903 * b + 23660 * a + d)
                                    if (27304196 == 4684659 * c + 803760 * b + 137903 * a + d)
                                        if (159140519 == 27304196 * c + 4684659 * b + 803760 * a + d)
                                            cout << a << " " << b << " " << c << " " << d << endl;
                    }
                        

                }
            }
        }
    }
    return 0;
}

打出来一个式子的系数为:0 -1 6 2(当然其他的系数也可以,比如:1 -7 7 0),所对应的式子是: f [ i ] = 6 ∗ f [ i − 1 ] − f [ i − 2 ] + 2 f[i] = 6 * f[i - 1] - f[i - 2] + 2 f[i]=6f[i1]f[i2]+2(或者: f [ i ] = f [ i − 3 ] − 7 f [ i − 2 ] + 7 f [ i − 1 ] f[i] = f[i - 3] - 7f[i - 2] + 7f[i - 1] f[i]=f[i3]7f[i2]+7f[i1])。然后对于每一个L,暴力枚举大于等于L的第一个n。

代码:

// f[i] = 6 * f[i - 1] - f[i - 2] + 2
import java.io.*;
import java.math.*;
import java.util.*;
import java.text.*;

public class Main {
    public static void main(String[] args) {
        Scanner cin = new Scanner (new BufferedInputStream(System.in));
        int T = cin.nextInt();
        while((T--) != 0) {
            BigInteger a = new BigInteger("3");
            BigInteger b = new BigInteger("20");
            BigInteger n = cin.nextBigInteger();
            if (n.compareTo(new BigInteger("3")) <= 0) {
                System.out.println("3");
                continue;
            }
            else if (n.compareTo(new BigInteger("20")) <= 0) {
                System.out.println("20");
                continue;
            }
            for (int i = 1; i <= 10000; ++i) {
                BigInteger d = ((b.multiply(new BigInteger("6"))).subtract(a)).add(new BigInteger("2"));
                if (d.compareTo(n) >= 0) {
                    System.out.println(d);
                    break;
                }
                a = b;
                b = d;
            }
        }
    }
}
// f[i] = 7 * f[i - 1] - 7 * f[i - 2] + f[i - 3]
import java.io.*;
import java.math.*;
import java.util.*;
import java.text.*;

public class Main {
    public static void main(String[] args) {
        Scanner cin = new Scanner (new BufferedInputStream(System.in));
        int T = cin.nextInt();
        while((T--) != 0) {
            BigInteger a = new BigInteger("3");
            BigInteger b = new BigInteger("20");
            BigInteger c = new BigInteger("119");
            BigInteger n = cin.nextBigInteger();
            if (n.compareTo(new BigInteger("3")) <= 0) {
                System.out.println("3");
                continue;
            }
            else if (n.compareTo(new BigInteger("20")) <= 0) {
                System.out.println("20");
                continue;
            }
            else if (n.compareTo(new BigInteger("119")) <= 0) {
                System.out.println("119");
                continue;
            }
            for (int i = 1; i <= 10000; ++i) {
                BigInteger d = a.add(c.multiply(new BigInteger("7"))).subtract(b.multiply(new BigInteger("7")));
                if (d.compareTo(n) >= 0) {
                    System.out.println(d);
                    break;
                }
                a = b;
                b = c;
                c = d;
            }
        }
    }
}

M. The Maximum Unreachable Node Set

题意: 给的一张DAG,问这个DAG中可以选出多少个点,这些点互相不可达。

题解: 本题是有重复点的DAG的最小路径覆盖问题。固定套路:传递闭包,然后求最小路径覆盖。本题和《算法竞赛进阶指南》的捉迷藏一题一模一样,看来出题人有点懒呀。

代码:

#include <bits/stdc++.h>

using namespace std;

int n, m;
int const N = 2e2 + 10, M = 3e4 + 10;
int match[N], vis[N], d[N][N];

// 二分匹配
int find(int x) {
    for (int i = 1; i <= n; ++i) {
        if (vis[i] || !d[x][i]) continue;
        vis[i] = 1;
        if (!match[i] || find(match[i])) {
            match[i] = x;
            return true;
        }
    }
    return false;
}

int main()
{
    int T;
    cin >> T;
    while(T--) {
        cin >> n >> m;
        memset(match, 0, sizeof match);
        memset(d, 0, sizeof d);
        // 读入边
        for (int i = 1; i <= m ;++i) {
            int a, b;
            scanf("%d %d", &a, &b);
            d[a][b]  = 1;
        }

        // 传递闭包
        for(int k = 1; k <= n; ++k)
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= n; ++j)
                    d[i][j] |= d[i][k] & d[k][j];
        
        int ans = 0;
        // 二分匹配,这里把原来的边i复用为新的边i',因为二分图内左半部分内部不会有连线
        for (int i = 1; i <= n; ++i) {
            memset(vis, 0, sizeof vis);
            if (find(i)) ans++;
        }
        cout << n - ans << endl;
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值