• 2021,8,23 模拟赛


    前言:

    (T1) 逆元。

    (T2) 没过大样例竟然跑过去了 = =

    (T3) 没开 (long ~long) 含泪挂 0

    期望: (Soce: 20 + 60 + 10)

    实际: (Soce: 20 + 100 + 0)

    T1 count

    题面

    一道 sb 数论题。

    题目大意:

    问有几个无序二元组 ((x, y)) 满足 (x y ≡ 1 pmod p), (0 ≤ x < P, 0 ≤ y <P)

    solution

    发现 (xy equiv 1(mod~p)) 实际上就是 (x) 逆元的定义式。

    (x) 有逆元当且仅当 (gcd(x, p) = 1) 的时候,并且逆元是唯一的。

    (1sim P - 1) 中与 (P) 互质的数有 (s) 个,考虑这 (s) 个数与它

    们的逆元组成的二元组,这些二元组一定符合条件,那么只要考

    虑去重的问题。

    可以发现如果 (xy equiv 1(~mod~p), x eq y) 那么 ((x,y))((y,x))

    一定会在上述 s 个二元组中各出现一次,也就是被多算了一次。

    (x = y) 的时候,((x, x)) 这个二元组只会出现一次,设个数为 (t) 个。

    答案就是 (frac{s + t}{2})

    (1sim P - 1) 中与 (P) 互质的数的个数用欧拉函数来求就好。

    code

    /*
    work by:Ariel_
    Knowledge:
    Time:
    */
    #include <bits/stdc++.h>
    #define ll long long
    #define rg register
    using namespace std;
    int p, Ans;
    int read(){
        int x = 0,f = 1; char c = getchar();
        while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
        return x*f;
    }
    int phi(int m) {
       int res = m, tmp = m;
       for (int i = 2; i * i <= m; i++) {
       	   if (tmp % i == 0) {
       	      res = res / i * (i - 1);
    		  while(tmp % i == 0) tmp /= i;
    	   }
       }
       if (tmp > 1) res = res / tmp * (tmp - 1);
       return res;
    }
    int main(){
    	freopen("count.in", "r", stdin);
        freopen("count.out", "w", stdout);
    	p = read();
    	Ans += phi(p);
    	for(int i = 1; i <= p; i++) 
    	if(i * i % p == 1) Ans++;
    	Ans = Ans / 2;
    	printf("%d
    ", Ans);
    	return 0;
    }
    

    T2 color (CF547D Mike and Fish)

    一道 CF 原题

    题面

    solution

    对于 (x) 坐标相同的点,把这些点两两配对,如果剩下点则不管,然后在每一对点之间连一条边。

    对于 (y) 坐标相等的点,把这些点两两配对,如果剩下点则不管,然后在每一对点之间连一条边。

    最后对得到的图进行黑白染色可以得到我们的方案了。

    证明:如果我们红蓝染色成功,那么对于每一个 (x) 或者 (y) 坐标来讲最多剩一个点,这个点的颜色可以任意选择。

    code

    /*
    work by:Ariel_
    Knowledge:
    Time:
    */
    #include <bits/stdc++.h>
    #define rg register
    using namespace std;
    const int N = 500010;
    int read(){
        int x = 0,f = 1; char c = getchar();
        while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
        return x*f;
    }
    int n, x, y, col[N];
    map<int, int>lx, ly;
    int head[N], E;
    char c[2];
    struct edge{int v, nxt;}e[N << 1];
    void add_edge(int u, int v) {
      e[++E] = (edge) {v, head[u]};
      head[u] = E;
    }
    void dfs(int u, int f){
       for(int i = head[u]; i; i = e[i].nxt) {
       	  int v = e[i].v;
       	  if (v == f) continue;
       	  if(col[v] == -1) {
       	     col[v] = col[u] ^ 1;
    		 dfs(v, u);	
    	  }
       }
    }
    int main(){
       //freopen("color.in", "r", stdin);
       //freopen("color.out", "w", stdout);
       n = read(); 
       memset(col, -1, sizeof col);
       c[0] = 'r', c[1] = 'b';
       for (int i = 1; i <= n; i++) {
       	  x = read(), y = read();
       	  if(lx[x]) {
       	  	add_edge(lx[x], i), add_edge(i, lx[x]);
    	    lx[x] = 0;
    	  } else lx[x] = i;
       	  if (ly[y]) {
       	  	add_edge(ly[y], i), add_edge(i, ly[y]);
    		ly[y] = 0;
    	  } else ly[y] = i;
       }
       for (int i = 1; i <= n; i++) 
       if(col[i] == -1) col[i] = 1, dfs(i, 0);
       for (int i = 1; i <= n; i++) printf("%c", c[col[i]]);
       puts("");
       return 0;
    }
    /*
    10
    1 1
    2 1
    2 2
    3 2
    4 3
    4 4
    3 3
    2 3
    2 4
    1 3 
    */
    

    T3 Sequence

    很有价值的一道题。

    题面

    给出一个 (n) 个数的序列,一个区间的价值是区间内最小值乘最大值的积。

    (nleq 10^5)

    考试的时候一直在考虑每个数对哪些区间有贡献,把贡献对应到二维平面上,求解。

    写数据结构写多了吧 = = 没注意到 (nleq 10^5)

    感觉题解解释的就很详细了,所以就直接搬来了。(其实是我懒

    subtask1

    (O(n^2)) 暴力

    subtask2

    数据是随机的。

    考虑分治。

    如果当前是在考虑区间 ([l,r]) 的子区间的价值和,设区间 ([l, r]) 内最大值的下标为 (mid)

    考虑左端点在 ([l, mid]) 内,右端点在 ([mid,r ]) 内的区间的价值和。它们内部的最大值是 (a_{mid})

    ([l, mid]) 做后缀 (min) ,对 ([mid, r]) 做前缀 (min) ,不妨把结果记为(mn[i]) 。即 (lleq ileq mid) 时,(mn[i] = min_{i = l}^{mid }a_i)(mid < i leq r)(mn[i] = min_{i = mid}^r a_i)

    枚举左端点,由于右半部分的 (mn) 是单调的,一定存在一个分界点 (p) ,使得 (j leq p) 时,(mn[j] geq mn[i])(j > p)(mn[j] < mn[i]) 。又由于左半部分的 (mn) 也是单调的,所以左端点 (i) 移动的时,分界点 (p) 的移动方向是不变的。这样就容易对每个左端点 (i) 都求出分界点 (p) 了。另外再求出 (mn) 的前缀和,就能 (O(1)) 求出左端点在 (i) , 右端点在 ([mid, r]) 内的区间的价值和了。

    然后递归计算区间 ([l, mid - 1])([mid + 1, r])

    时间复杂度 (O(nlogn))

    Subtask3

    只有 10 种数,因此左端点固定时,最大值至多只有 10 种,

    即右端点在某段区间内的最大值相同,而这种区间最多只有 10 个。

    最小值同理。同时考虑最大值和最小值可以知道,左端点固定时,右端点在某段区间内的最大值和最小值都相同,而这种区间最多只有 20 个。

    移动左端点,用单调栈维护这些区间。复杂度 (O(n imes 20))

    Subtask5

    Subtask2 的做法类似,每次取 $mid = lfloor frac{l + r}{2} floor $,对最大值进行和最小值类似的操作(不妨把前缀/后缀 (max) 的结果记为 (mx[i]) )。我们可以求出最大值的分界点 (q)。除了 (mn) 的前缀和外,再求出 (mx) 的前缀和以及 (mn imes mx) 的前缀和。(p)(q) 把右半部分划分成三个区间,对三个区间分别计算。利用求出来的前缀和可以 (O(1)) 算出右端点在一个区间内的价值和。

    然后再递归计算左右两半部分。

    code

    /*
    work by:Ariel_
    Knowledge:
    Time:
    */
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <algorithm>
    #define ll long long
    #define rg register
    using namespace std;
    const ll mod = 998244353;
    const int MAXN = 1e6 + 5;
    int read(){
        int x = 0,f = 1; char c = getchar();
        while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
        return x*f;
    }
    int n, a[MAXN], Ans, mn[MAXN], mx[MAXN], s1[MAXN], s2[MAXN], s3[MAXN];
    void work(int l, int r) {
       if (l == r) {
       	  Ans = (Ans + 1ll * a[l] * a[r] % mod) % mod;
       	  return ;
       }
       int mid = l + r >> 1;
       mn[mid] = a[mid];
       for (int i = mid - 1; i >= l; i--) mn[i] = min(mn[i + 1], a[i]);
       mx[mid] = a[mid];
       for (int i = mid - 1; i >= l; i--) mx[i] = max(mx[i + 1], a[i]);
       mn[mid + 1] = a[mid + 1];
       for (int i = mid + 2; i <= r; i++) mn[i] = min(mn[i - 1], a[i]);
       mx[mid + 1] = a[mid + 1];
       for (int i = mid + 2; i <= r; i++) mx[i] = max(mx[i - 1], a[i]);
       s1[mid] = 0;
       for (int i = mid + 1; i <= r; i++) s1[i] = (s1[i - 1] + mn[i]) % mod;
       s2[mid] = 0;
       for (int i = mid + 1; i <= r; i++) s2[i] = (s2[i - 1] + mx[i]) % mod;
       s3[mid] = 0;
       for (int i = mid + 1; i <= r; i++) s3[i] = (s3[i - 1] + 1ll * mx[i] * mn[i] % mod) % mod;
       int p1 = mid + 1, p2 = mid + 1;
       for (int i = mid; i >= l; i--) {
       	 while (p1 <= r && mn[p1] > mn[i]) p1++;
       	 while (p2 <= r && mx[p2] < mx[i]) p2++;
       	 if (p1 < p2) {
       	     Ans = (Ans + 1ll * mn[i] * mx[i] % mod * (p1 - 1 - mid) % mod) % mod;
    	     Ans = (Ans + 1ll * mx[i] * (s1[p2 - 1] - s1[p1 - 1] + mod) % mod) % mod;
    		 Ans = (Ans + (s3[r] - s3[p2 - 1] + mod) % mod) % mod;	
    	  }
    	  else {
    	  	Ans = (Ans + 1ll * mn[i] * mx[i] % mod * (p2 - 1 - mid) % mod) % mod;
    	  	Ans = (Ans + 1ll * mn[i] * (s2[p1 - 1] - s2[p2 - 1] + mod) % mod) % mod;
    	  	Ans = (Ans + (s3[r] - s3[p1 - 1] + mod) % mod) % mod;
    	  }
       }
       work(l, mid), work(mid + 1, r);
    }
    int main(){
       n = read();
       for (int i = 1; i <= n; i++) a[i] = read();
       work(1, n);
       printf("%d", Ans);
       puts("");
       return 0;
    }
    
    
  • 相关阅读:
    php字符串处理函数大全 转
    php http Manual
    HTTP协议详解 百度文库
    PHP获取指定日期的上个月的日期 转
    计算指定日期的前N个月日期
    MySQL之count(*)与count(id)效率比较<转>
    生成器
    可迭代对象_迭代器
    变量内容的删除替换和替代
    软件磁盘阵列
  • 原文地址:https://www.cnblogs.com/Arielzz/p/15177813.html
Copyright © 2020-2023  润新知