ACM-ICPC 2018 徐州赛区网络预赛

ACM-ICPC 2018 徐州赛区网络预赛

  • A. Hard to prepare
  • B. BE, GE or NE
  • C. Cacti Lottery
  • D. Easy Math
  • E. End Fantasy VIX
  • F. Features Track
  • G.Trace
  • H. Ryuji doesn’t want to study
  • I. Characters with Hash
  • J. Maze Designer
  • K. Morgana Net

A. Hard to prepare

题意: N N N 个客人 2 K 2^K 2K 顶帽子, 每顶帽子编号为 0 ∼ 2 K − 1 0 \sim 2^K - 1 02K1 N N N 个客人围成一个圈, 要求相邻两个客人的帽子编号(K位二进制)同或不为0。测试样例T<=20, 0<N,k<1e6
题解: 分类递推
f 1 [ n ] : f1[n]: f1[n]: 长度为n的序列任意两个至少有一位相同的方案数(不保证首位不相同)
f 2 [ n ] : f2[n]: f2[n]: 长度为n的序列,首位完全不同的方案
a n s = f 1 [ n ] − f 2 [ n ] ans = f1[n] - f2[n] ans=f1[n]f2[n]
转移方程:
f 1 [ n ] = f 1 [ n − 1 ] ∗ ( 2 k − 1 ) f1[n] = f1[n - 1] * (2 ^k - 1) f1[n]=f1[n1](2k1)
f 2 [ n ] = f 1 [ n − 1 ] − ( f 1 [ n − 2 ] − f 2 [ n − 2 ] ) f2[n] = f1[n - 1] - (f1[n - 2] - f2[n - 2]) f2[n]=f1[n1](f1[n2]f2[n2]) (注解:f1[n-1]-1和n-1完全相同的方案数)
入口:
f 1 [ 1 ] = 2 k , f 2 [ 1 ] = 0 , f 1 [ 2 ] = 2 k ∗ ( 2 k − 1 ) , f 2 [ 2 ] = 0 f1[1] = 2^k, f2[1]=0, f1[2] = 2^k * (2^k - 1), f2[2] = 0 f1[1]=2k,f2[1]=0,f1[2]=2k(2k1),f2[2]=0
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e6 + 10, mod = 1e9 + 7;
LL n, k, T, f1[N], f2[N];
map<pair<int, int>, LL> mp;

LL qmi(LL a, LL k, LL p){
    LL res = 1 % p;
    while(k) {
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res % p;
}

int main() {
    cin >> T;
    while(T--) {
        scanf("%lld%lld", &n, &k);
        if (mp.count({n, k})) {
            printf("%lld\n", mp[{n, k}]);
            continue;
        }
        memset(f1, 0, sizeof f1);
        memset(f2, 0, sizeof f2);
        
        f1[1] = qmi(2, k, mod);
        f1[2] = f1[1] * (qmi(2, k, mod) - 1) % mod;
        
        for (int i = 3; i <= n; ++i) {
            f1[i] = f1[i - 1] * (qmi(2, k, mod) - 1 + mod) % mod;
            f2[i] = (((f1[i - 1] - f1[i - 2]) % mod + mod) % mod + f2[i - 2]) % mod;
        }
        
        LL res = f1[n] - f2[n];
        res = (res % mod + mod) % mod;
        printf("%lld\n", res);
        mp[{n, k}] = res;
    }
	return 0;
}

B. BE, GE or NE

题意: 两个人的博弈,一开始有一个初始分数值,有三种操作,分别为+a,-b和变为相反数,先手希望走向Good Ending,后手希望走向Bad Ending,如果不能的话都希望走向Normal Ending。两人都以最优策略进行游戏。求问n轮过后结局是哪一种。
题解: 博弈论的经典题目(算是吧,做法挺经典的)
首先我们知道对于某一个状态,其必胜态条件是子状态中至少有一个其必败态,其必败态条件是子状态中全为必胜态。
对于这种最朴素的问题,我们就采用对对应状态的记忆化搜索来解决,这是一种经典解法。
对于此题来讲,我们的状态就是当前为第几回合和当前分数的组合。
我们定义先手胜利为0,后手胜利为2,平局为1.
那么对于先手,肯定是取后面的状态中返回值最小的,而对于后手,肯定是取后面的状态中返回值最大的,因为能有利于自己的话,采取最优策略一定不会选择去往平局上靠。
用一个二维数组记忆化一下就可以了。
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e6 + 10;
int T, n, f[1010][250], a[1010], b[1010], c[1010], m, up, lower;
unordered_map<int, int> id;

int dfs(int cnt, int score) {
    if (cnt == n + 1) {
        if (score >= up) return 0;
        else if (score <= lower ) return 2;
        else return 1;
    }
    if (f[cnt][id[score]] != -1) return f[cnt][id[score]];
    int ans;
    if (cnt & 1) {
        ans = 2;
        if (a[cnt]) ans = min(ans, dfs(cnt + 1, min(100, score + a[cnt])));
        if (b[cnt]) ans = min(ans, dfs(cnt + 1, max(-100, score - b[cnt])));
        if (c[cnt]) ans = min(ans, dfs(cnt + 1, -score));
    }
    else {
        ans = 0;
        if (a[cnt]) ans = max(ans, dfs(cnt + 1, min(100, score + a[cnt])));
        if (b[cnt]) ans = max(ans, dfs(cnt + 1, max(-100, score - b[cnt])));
        if (c[cnt]) ans = max(ans, dfs(cnt + 1, -score));
    }
    return f[cnt][id[score]] = ans;
}

int main() {
    cin >> n >> m >> up >> lower;
    memset(f, -1, sizeof f);
    for (int i =1 ; i <= n; ++i) scanf("%d%d%d", &a[i], &b[i], &c[i]);
    for (int i = -100; i <= 100; ++i) {
        id[i] = i + 100;
    }
    int ans = dfs(1 , m);
    if (ans == 0) {
        cout << "Good Ending\n";
    }
    else if (ans == 1) {
        cout << "Normal Ending\n";
    }
    else cout << "Bad Ending\n";
	return 0;
}

D. Easy Math

题意: 给定n,m,求解 ∑ i = 1 m μ ( i ∗ n ) , 1 < = m < = 2 ∗ 1 0 9 , 1 < = n < = 1 0 12 \sum_{i=1}^{m} \mu(i * n), 1 <= m <= 2*10^9, 1 <= n <= 10^{12} i=1mμ(in),1<=m<=2109,1<=n<=1012
题解:
Screenshot_20200918_230901.jpg
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e7 + 10;
LL n, m;
int prime[N], cnt, mu[N], st[N];

void init(int n) {
    mu[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!st[i]) prime[cnt++] = i, mu[i] = -1;
        for (int j = 0; prime[j] <= n / i; ++j) {
            st[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= n; ++i) mu[i] += mu[i - 1];
    return ;
}

int get_mu(LL x) {
    if (x == 1) return 1;
    int num = 0;
    for (int i = 2; i <= x / i; ++i) {  // 因为一个数x的所有因子内只会有一个大于sqrt(x), 所以只要枚举到sqrt(x),而后单独判断大于sqrt(x)的那个即可
        if (x % i == 0) {  // 如果能够整除
            int s = 0;  // 记录能够整除几次
            while (x % i == 0) {
                s++;
                x /= i;
                if (s >= 2) return 0;
            }
            num++;
        }
    }
    if (x > 1) num++;
    if (num & 1) return -1;
    else return 1;
}

LL S(LL m) {
    if (m < N) return mu[m];
    LL res = 0;
    for (int l = 2, r; l <= m; l = r + 1) {
        r = min(m, m / (m / l));
        res += (r - l + 1) * S(m / l);
    }
    return 1 - res;
}

LL F(LL n, LL m) {
    if (n == 1) return S(m);
    LL tmp = 0;
    for (int i = 1; i <= n / i && i <= m; ++i) {
        if (n % i == 0) {
            tmp += get_mu(i) * F(i, m / i);
            if (i != n / i) tmp += get_mu(n / i) * F(n / i, m / (n / i));
        }
    }
    return tmp * get_mu(n);
}

int main() {
    init(N - 1);
    scanf("%lld%lld", &m, &n);
    printf("%lld\n", F(n, m));
	return 0;
}

F. Features Track

题意: 给定很多个点,求能够在能够在连续的几帧出现的点最多出现在连续几帧
题解: 给每个点用一个队列存一下在哪几帧出现过,然后遍历每个队列,判断即可
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e5 + 10;
int T, n, cnt;
vector<int> v[N];
map<PII, int> mp;
map<PII, int> vis;

int mapping(PII t) {
    if (mp.count(t)) return mp[t];
    else return mp[t] = ++cnt;
}

int get_longest(vector<int> x) {
    int maxv = 1, tmp = 1;
    int len = x.size();
    for (int i = 0; i < len - 1; ++i) {
        if (x[i + 1] == x[i] + 1) tmp ++;
        else {
            maxv = max(maxv, tmp);
            tmp = 1;
        }
    }
    return maxv;
}

int main() {
    cin >> T;
    while(T--) {
        int v_cnt = 0;
        mp.clear();
        cnt = 0;
        scanf("%d", &n);
        for (int i = 1, len; i <= n; ++i) {
            vis.clear();
            scanf("%d", &len);
            if (!len) continue;
            for (int j = 1, a, b; j <= len; ++j) {
                scanf("%d%d", &a, &b);
                if (vis.count({a, b})) continue;
                else vis[{a, b}] = 1;
                v_cnt = mapping({a, b});
                v[v_cnt].push_back(i);
                // cout << v_cnt << endl;
            }
        }
        // cout << cnt << endl;
        int maxv = -1;
        for (int i = 1; i <= cnt; ++i) {
            int tmp = get_longest(v[i]);
            maxv = max(maxv, tmp);
        }
        for (int i = 1; i <= cnt; ++i) v[i].clear();
        printf("%d\n", maxv);
    }
    return 0;
}

G.Trace

题意: 给定许多个海浪,每次新的海浪会覆盖前一个海浪的痕迹,但是不会出现两个海浪完全覆盖的情形,求所有海浪过后海滩上留下的痕迹总长。
题解: 分析可知,前一个海浪会被后一个海浪的痕迹覆盖,最后一个海浪的痕迹会被完全留下来,因此需要倒着分析。每次到找到一个小于当前x或者y的海浪,然后x和这个海浪的差值累加到答案中.
代码:

#include <bits/stdc++.h>

typedef long long LL;
using namespace std;

int const N = 5e4 + 10;
int x[N], y[N], n;
set<int> sx, sy;

int main() {
    cin >> n;
    for (int i = 1, a, b; i <= n; ++i) {
        scanf("%d%d", &a, &b);
        x[i] = a, y[i] = b;
    }
    LL res = 0;
    sx.insert(0), sy.insert(0);
    for (int i = n; i >= 1; --i) {
        auto it = sx.lower_bound(x[i]);
        res += x[i] - (*(--it));
        
        it = sy.lower_bound(y[i]);
        res += y[i] - (*(--it));
        
        sx.insert(x[i]), sy.insert(y[i]);
    }
    cout << res << endl;
    return 0;
}

H. Ryuji doesn’t want to study

题意: 一开始给定n个数,m次询问。每次询问两种类型:
1 x y, 表示询问a[l]*len + a[l + 1] * (len - 1) + … + a[r] * 1,len = r - l + 1。
2 x y, 表示把a[x]赋值为y
n ~ 1e5, m ~ 1e5
题解:
∑ i = l r a [ i ] ∗ ( r − i + 1 ) = ( r + 1 ) ∑ i = l r a [ i ] − ∑ i = l r i ∗ a [ i ] \sum_{i=l}^ra[i]*(r - i + 1) = (r+1)\sum_{i=l}^ra[i] - \sum_{i=l}^ri * a[i] i=lra[i](ri+1)=(r+1)i=lra[i]i=lria[i]
因此,可以使用树状数组分别维护a[i]和i * a[i]的前缀和即可
代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

int n, m;
int const N = 1e5 + 10;
LL c1[N], c2[N], a[N], sum1[N], sum2[N];  // c1[x]维护ai的前缀和,c2[x]维护i*ai的前缀和

LL lowbit(LL x) {
    return x & -x;
}

// 单点修改
void add(LL c[], int x, LL y) {
    for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
}

// 查询Sx前缀和
LL query(int x, LL c[]) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &a[i]);  // 读入a[i]
        sum1[i] = sum1[i - 1] + a[i];
        sum2[i] = sum2[i - 1] + i * a[i];
    }
    for (int i = 1; i <= n; ++i) {
        c1[i] = sum1[i] - sum1[i - lowbit(i)];
        c2[i] = sum2[i] - sum2[i - lowbit(i)];
    }
    while (m--) {
        LL op, x, y;
        scanf("%lld%lld%lld", &op, &x, &y);
        if (op == 1) {  // 询问
            printf("%lld\n", (y + 1) * (query(y, c1) - query(x - 1, c1)) - (query(y, c2) - query(x - 1, c2)));
        }
        else {  // 单点修改
            add(c1, x, y - a[x]), add(c2, x, x * y - x * a[x]);
            a[x] = y;
        }
    }
    return 0;
}

I. Characters with Hash

题意: 签到
题解:
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int T, n;

int main() {
    cin >> T;
    while(T--) {
        scanf("%d", &n);
        char c;
        cin >> c;
        string s;
        cin >> s;
        int res = 0;
        int j = 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] != c) break;
            else j++;
        }
        // cout << j << endl;
        if (j == s.size()) cout << 1 << endl;
        else {
            int tmp = abs(s[j] - c);
            if (tmp < 10) res += 1;
            else res += 2;
            res += (s.size() - j - 1) * 2;
            cout << res << endl;
        }
    }
    return 0;
}

J. Maze Designer

题意: 给定一个n * m的方格,每个方格和下方和右方的方格有一条边,权值给定。要求将方格n * m个点连成一个最大生成树,然后给定任意两个点,求他们之间的距离。查询个数1e5
题解: 先求出最大生成树(树的权值最大,cnt-树的权值 最小),然后lca查询
代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 510 * 510, M = N * 2 * 2;
int f[N][20], d[N], dist[N];  // f[i][j]表示从i开始,往上走2^j步到达的点,d为深度,dist为距离
int e[M], ne[M], h[N], idx, w[M], cnt, fa[N];
int T, n, m, Q, t;  // t为数的深度
queue<int> q;
struct Edge{
    int u, v, w;
    bool operator<(const Edge &e)const {
        return w > e.w;
    }
}edge[M];
vector<int> tree_edge;

void add(int a, int b, int c) {
    e[idx] = b,  w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// 预处理:得到每个点的深度,距离,f数组
void bfs() {
    q.push(1);  // 把根放入队列,注意这里有可能根不是1
    d[1] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        for (int i = h[x]; i != -1; i = ne[i]) {
            int y = e[i];
            if (d[y]) continue;
            d[y] = d[x] + 1;  // 更新深度
            dist[y] = dist[x] + w[i];  // 更新距离
            
            // 进行dp更新
            f[y][0] = x;
            for (int j = 1; j <= t; ++j) {
                f[y][j] = f[f[y][j - 1]][j - 1];  // 分两段处理
            }
            q.push(y);
        }
    }
}

// 查找x和y的最近公共祖先
int lca(int x, int y) {
    if (d[x] > d[y]) swap(x, y);  // 保证x的深度浅一点
    for (int i = t; i >= 0; --i)
        if (d[f[y][i]] >= d[x]) y = f[y][i];  // 让x和y到同一个深度
    if (x == y) return x;
    for (int i = t; i >= 0; --i) {// 让x和y之差一步就能相遇
        if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}

int get(int x) {
    if (x == fa[x]) return x;
    int root = get(fa[x]);
    return fa[x] = root;
}

void kurskal(){
    sort(edge, edge + cnt);
    for (int i = 0; i < cnt; ++i) {
        int a = edge[i].u, b = edge[i].v;
        a = get(a), b = get(b);
        if (a != b) {
            fa[a] = b;
            tree_edge.push_back(i);
        }
    }
    return;
}

int main() {
    cin >> n >> m;
    getchar();
    memset(h, -1, sizeof h);
    for (int i = 0; i < n * m; ++i) {
        fa[i + 1] = i + 1; 
        char op1, op2;
        int d, r;
        scanf("%c %d %c %d", &op1, &d, &op2, &r);
        getchar();
        int cur = i + 1, right = cur + 1, down = cur + m;
        if (op1 != 'X') edge[cnt++] = {cur, down, d};
        if (op2 != 'X') edge[cnt++] = {cur, right, r};
    }
    
    kurskal();
    for (int i = 0; i < tree_edge.size(); ++i) {
        int a = edge[tree_edge[i]].u, b = edge[tree_edge[i]].v, c = edge[tree_edge[i]].w;
        add(a, b, 1), add(b, a, 1);
    }
    
    idx = t = 0;
    t = (int)(log(n * m) / log(2)) + 1; // 得到树的深度
    
    cin >> Q;
    bfs();
    
    for (int i = 1; i <= Q; ++i) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        x1 --, y1--, x2--,y2--;
        int a = x1 * m + y1 + 1, b = x2 * m + y2 + 1;
        printf("%d\n", dist[a] + dist[b] - 2 * dist[lca(a, b)]);
    }
    return 0;
}

K. Morgana Net

题意: 给定 N ∗ N N * N NN的A矩阵和 ( 2 m + 1 ) ∗ ( 2 m + 1 ) (2m+1)*(2m+1) (2m+1)(2m+1)的B矩阵,给定 f f f函数, A ′ = f ( A , B ) A'=f(A, B) A=f(A,B),即新的A矩阵由老的A矩阵和B矩阵进行特殊运算后生成,给定运算关系如下:
微信截图_20200924143640.png
同时, f ( x ) = x f(x) = x%2 f(x)=x。问输入A矩阵和B矩阵,进行t次f运算后,产生的A矩阵中有多少个1?
题解: 上述f运算可以看作找出 A i j A_{ij} Aij,以其为中心,然后向上下左右找到一个长为(2m+1)的正方形,和B矩阵对应位置相乘累加之后赋值给 A i j A_{ij} Aij。按照这个计算性质,可以把A矩阵变成一个 1 ∗ ( n ∗ n ) 1*(n*n) 1(nn)的一维向量,然后构造一个 ( n ∗ n ) ∗ ( n ∗ n ) (n*n)*(n*n) (nn)(nn)的辅助矩阵C,求出 C t C^t Ct,再和A矩阵相乘计算1的数目即可。观察到对于A中的每一个元素,每一次卷积运算,所要求乘积的值的位置是固定的,那么我们就提前预处理出,对于A中的每一个元素求出参与该元素的卷积运算的对应元素的位置。
代码:

// 自己写的代码一直wa,放弃了,贴了份网上ac的代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>
using namespace std;
 
typedef long long ll;
const int MOD = 2;
const int MAXN = 40000;
 
typedef struct {
 
    bitset<80> m[80];
    int sizx,sizy;
 
}Matrix;
int n,m;
Matrix cb;
inline Matrix Mul(Matrix a, Matrix b)
{
    Matrix c;
 
    c.sizx=a.sizx;
    c.sizy=b.sizy;
    cb.sizy=b.sizx;
    cb.sizx=b.sizy;
    for(int i=0;i<b.sizy;i++)
    {
        cb.m[i].reset();
        for(int j=0;j<b.sizx;j++)
        {
            cb.m[i][j]=b.m[j][i];
        }
    }
    for (int i = 0; i < a.sizx; i++)
    {
        c.m[i].reset();
        for (int j = 0; j < b.sizy; j++)
        {
            bitset<80> tmp=(a.m[i]&cb.m[j]);
            c.m[i][j]=tmp.count()&1;
        }
    }
    return c;
}
 
inline Matrix fastm(Matrix a, ll num)
{
    Matrix res;
    for(int i=0;i<a.sizx;i++) res.m[i].reset();
    res.sizx=a.sizx;
    res.sizy=a.sizy;
    for(int i=0;i<a.sizx;i++)
        res.m[i][i]=1;
    while (num)
    {
        if (num & 1)
            res = Mul(res, a);
        num >>= 1;
        a = Mul(a, a);
    }
    return res;
}
 
 
Matrix a,b,c;
int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
 
        int t;
        scanf("%d%d%d",&n,&m,&t);
        a.sizx=1;a.sizy=n*n;
        a.m[0].reset();
        for(int i=0;i<n*n;i++)
        {
            int tmp;
            scanf("%d",&tmp);
            a.m[0][i]=tmp&1;
        }
 
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<m;j++)
            {
                int tmp;
                scanf("%d",&tmp);
                b.m[i][j]=tmp&1;
            }
        }
 
        c.sizx=c.sizy=n*n;
        int num=0;
        int in=0;
        m=m>>1;
        for(int i=0;i<n*n;i++) c.m[i].reset();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                for(int p=i-m;p<=i+m;p++)
                {
                    for(int q=j-m;q<=j+m;q++)
                    {
                        in=p*n+q;
                        if(p>=0&&p<n&&q>=0&&q<n)
                        {
                            c.m[in][num]=(b.m[p-i+m][q-j+m]);
                        }
 
                    }
 
                }
                num++;
            }
 
        }
        c=fastm(c,t);
        a=Mul(a,c);
 
        printf("%d\n",a.m[0].count());
    }
 
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值