2019牛客暑期多校训练营(第三场)

Contest Info


[Practice Link](https://ac.nowcoder.com/acm/contest/883#question)
Solved A B C D E F G H I J
8/10 Ø O - Ø - O O O Ø Ø
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. Graph Games

题意:
有一张\(n\)个点\(m\)条边的图,定义\(S(x)\)为和\(x\)这个点邻接的所有点的集合。
支持两种操作:

  • 翻转\([l, r]\)区间的边的状态,即已经在的话就删边,否则加边
  • 询问\(S(u)\)是否等于\(S(v)\)

思路:
首先显示的表示\(S(x)\),对每个点随机一个权值,加点删点都是异或。
将点按度数进行分块,分为小点和大点。
再将边序列分块:

  • 对于所有小点,块间打标记,块内暴力标记,查询的时候两个异或一下就知道当前边的状态
  • 对于所有大点,维护出每块和它邻接的边的点的异或值,然后块间翻转,两侧暴力。

代码:

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

#define ll long long
#define N 200010
#define U 1010
#define unit 600
#define pii pair <int, int>  
int n, m, q;
int vis[N], degree[N];
pii edge[N];  
vector <int> vec[N]; 
int pos[N], posl[U], posr[U]; 
int isHasUnit[U], isHasSin[N];
int id[N]; 
int valBig[U][U], valBigOri[U][U];  
int Hash[N], ran; 

void read(int &res)
{
    res = 0;
    char c;
    while (!isdigit(c = getchar()));
    while (isdigit(c)) res = res * 10 + c - '0', c = getchar();
}

void force(int l, int r)
{
    for (int i = l, u, v; i <= r; ++i)
    {
        isHasSin[i] ^= 1;
        u = edge[i].first, v = edge[i].second;                             
        if (vis[u])
            valBig[id[u]][pos[i]] ^= Hash[v];
        if (vis[v])
            valBig[id[v]][pos[i]] ^= Hash[u];
    }
}

void update(int l, int r) 
{
    if (pos[l] == pos[r]) force(l, r);
    else
    {
        force(l, posr[pos[l]]);
        for (int i = pos[l] + 1; i < pos[r]; ++i) isHasUnit[i] ^= 1;
        force(posl[pos[r]], r); 
    }
}

int query(int u)
{
    int res = 0;
    if (vis[u])
    {
        for (int i = 1; i <= pos[m]; ++i)
        {
            res ^= valBig[id[u]][i];
            if (isHasUnit[i]) res ^= valBigOri[id[u]][i];
        }
    }
    else
    {
        for (auto it : vec[u])
        {
            if (isHasSin[it] ^ isHasUnit[pos[it]])
            {
                int v = edge[it].first == u ? edge[it].second : edge[it].first;
                res ^= Hash[v];
            }
        }
    }
    return res;
}

void Run()
{    
    for (int i = 1; i <= 100000; ++i)
    {
        ran *= 233;
        ran += 17;    
        Hash[i] = ran; 
    }
    for (int i = 1; i <= 200000; ++i)
    {
        pos[i] = (i - 1) / unit + 1;
        if (i == 1 || pos[i] != pos[i - 1]) posl[pos[i]] = i;
        posr[pos[i]] = i;
    }
	int T; scanf("%d", &T);
    while (T--)
    {
		scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) degree[i] = 0, vis[i] = 0, vec[i].clear();
        for (int i = 1; i <= m; ++i) isHasSin[i] = 1; 
        for (int i = 1; i <= pos[m]; ++i) isHasUnit[i] = 0;
        id[0] = 0; 
        for (int i = 1, u, v; i <= m; ++i)
        {
            read(u), read(v);
            edge[i] = pii(u, v);
            ++degree[u];
            ++degree[v]; 
        }
        for (int i = 1; i <= n; ++i) if (degree[i] >= unit)
        {
            vis[i] = 1;
            id[i] = ++id[0];  
            for (int j = 1; j <= pos[m]; ++j)
                valBig[id[i]][j] = 0, valBigOri[id[i]][j] = 0;  
        }
        for (int i = 1, u, v; i <= m; ++i) 
         {
            u = edge[i].first; v = edge[i].second;
            if (vis[u])
            {
                valBig[id[u]][pos[i]] ^= Hash[v];
                valBigOri[id[u]][pos[i]] ^= Hash[v];
            }
            else vec[u].push_back(i);
            if (vis[v])
            {
                valBig[id[v]][pos[i]] ^= Hash[u];
                valBigOri[id[v]][pos[i]] ^= Hash[u];
            }
            else vec[v].push_back(i);
        } 
        read(q);
        for (int qq = 1, op, x, y; qq <= q; ++qq)
        {
            read(op), read(x), read(y);
            if (op == 1) update(x, y);
            else putchar((query(x) == query(y)) + '0');
        }
        puts("");
    }
}

int main()
{

    Run();
    return 0;
}

B. Crazy Binary String

题意:
有一个01串,要求选择一个最长的子串和子序列,使得其中\(0\)\(1\)的个数相同。

思路:

  • 子串:维护前缀和找一下相同值
  • 子序列:Min(0的个数,1的个数)

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 100010
int n;
char s[N];
int cnt[2], S[N];
 
map <int, int> mp;
 
int main() {
    while (scanf("%d", &n) != EOF) {
        scanf("%s", s + 1);
        for (int i = 1; i <= n; ++i) S[i] = 0;
        cnt[0] = cnt[1] = 0;
        int res[2] = {0, 0};
        for (int i = 1; i <= n; ++i) {
            ++cnt[s[i] - '0'];
            if (s[i] == '1') {
                --S[i];
            } else {
                ++S[i];
            }
            S[i] += S[i - 1];
        }
        res[1] = min(cnt[0], cnt[1]) * 2;
        mp.clear();
        mp[0] = 0;
        for (int i = 1; i <= n; ++i) {
            if (mp.find(S[i]) != mp.end()) {
                res[0] = max(res[0], i - mp[S[i]]);
            } else {
                mp[S[i]] = i;
            }
        }
        printf("%d %d\n", res[0], res[1]);
    }
    return 0;
}

D. Big Integer

题意:
定义\(A(n) = 11111(n\text{个1})\),给出\(p, n, m\)询问有多少\((i, j)\)满足\(A(i^j) \equiv 0 \bmod p\)

思路:
\(A(n)\)写成:

\[\begin{eqnarray*} A(n) = \frac{10^n - 1}{9} \bmod p \end{eqnarray*} \]

那么有:

\[\begin{eqnarray*} &&A(i^j) \equiv 0 \bmod p \\ &\leftrightarrow& 10^n \equiv 1 \bmod 9p \end{eqnarray*} \]

那么我们知道:

\[\begin{eqnarray*} 10^{\varphi(9p)} \equiv 1 \bmod 9p \end{eqnarray*} \]

那么我们要找一个循环节\(t\),显然有\(t\;|\;9p\),那么直接暴力枚举\(9p\)的质因子,一个一个试着去掉就好了。
那么现在需要统计有多少对\((i, j)\)满足\(A(i^j) \equiv 0 \bmod p\)
我们先令

\[\begin{eqnarray*} t = p_1^{q_1}p_2^{q_2} \cdots p_k^{q_k} \end{eqnarray*} \]

那么对于一个\(j\)\(i\)需要满足\(g\;|\;i\),即:

\[\begin{eqnarray*} g = p_1^{\left\lceil \frac{q_1}{j} \right\rceil} \cdots p_k^{\left\lceil \frac{q_k}{j} \right\rceil} \end{eqnarray*} \]

并且考虑到\(q_i\)的最大取值只有\(30\),所以\(j > 30\)的情况和\(j = 30\)的情况是一样的,所以只需要枚举\(30\)次。

代码:

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

#define N 100010
#define ll long long
#define pii pair <int, int>
#define fi first
#define se second
ll p, n, m;
pii fac[N];
int tot;  

struct node {
	ll a[2][2];
	node() {
		memset(a, 0, sizeof a);
	}
	void set() {
		a[0][0] = a[0][1] = 1;
	}
	node operator * (const node &other) const {
		node res = node();
		for (int i = 0; i < 2; ++i) {
			for (int j = 0; j < 2; ++j) {
				for (int k = 0; k < 2; ++k) {
					res.a[i][j] = (res.a[i][j] + a[i][k] * other.a[k][j] % p) % p;
				}
			}
		}
		return res;
	}
}base, res; 

ll eular(ll n) {
	ll ans = n;
	for (int i = 2; i * i <= n; ++i) {
		if (n % i == 0) {
			ans -= ans / i;
			while (n % i == 0) 
				n /= i;
		}
	}
	if (n > 1) ans -= ans / n;
	return ans;
}

node qmod(node base, ll n) {
	node res = node(); res.set();
	while (n) {
		if (n & 1) {
			res = res * base;
		}
		base = base * base; 
		n >>= 1;
	}
	return res;
}

ll qpow(ll base, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1) {
			res = res * base;
		}
		base = base * base;
		n >>= 1;
	}
	return res;
}

void getfac(pii *fac, int &tot, ll x) {
	tot = 0;
	for (int i = 2; i * i <= x; ++i) {
		if (x % i == 0) {
			fac[++tot] = pii(i, 0);
			while (x % i == 0) {
				++fac[tot].se;
				x /= i;
			}
		}
	}
	if (x != 1) fac[++tot] = pii(x, 1);
	
}

ll calc(ll t, ll n, ll m) {
	ll res = 0;
	getfac(fac, tot, t);
	for (int j = 1; j <= min(30ll, m); ++j) {
		ll g = 1;
		for (int o = 1; o <= tot; ++o) {
			int a = fac[o].fi, b = fac[o].se;
			g *= qpow(a, b / j + (b % j != 0)); 
		}
		res += (n / g);
		if (j == 30) res += (n / g) * (m - j);
	}
	return res;
}

int main() {
	base = node();
	base.a[0][0] = 10;
	base.a[1][0] = 1;
	base.a[1][1] = 1;
	int T; scanf("%d", &T);
	while (T--) {
		scanf("%lld%lld%lld", &p, &n, &m);
		if (p == 2 || p == 5) {
			puts("0");
		} else {  
			ll t = eular(p * 9);   
		    getfac(fac, tot, t);	
			for (int i = 1; i <= tot; ++i) {
				for (int j = 1; j <= fac[i].se; ++j) {
					res = qmod(base, t / fac[i].fi - 1);
					if (res.a[0][0] == 0) {
						t = t / fac[i].fi;
					} else {
						break;
					}
				}
			}
			printf("%lld\n", calc(t, n, m));
		}
	}
	return 0;
}

F. Planting Trees

题意:
在一个\(n \cdot m\)的矩形中,每个点有权值\(a_{i,j}\),现在要找到一个最大的矩形,使得矩形内部权值的最大值和最小值之差不超过\(M\)

思路:
枚举上下界,单调队列维护最大最小值。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 510
#define INF 0x3f3f3f3f
#define pii pair <int, int>
#define fi first
#define se second
int a[N][N];
int g[2][N], que[2][N], l[2], r[2];
 
int main() {
    int n, m;
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                scanf("%d", &a[i][j]);
            }
        }
        int res = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                g[0][j] = -INF;
                g[1][j] = INF;
            }
            for (int j = i; j <= n; ++j) {
                l[0] = l[1] = 1;
                r[0] = r[1] = 0;
                int pos = 1;
                for (int k = 1; k <= n; ++k) {
                    g[0][k] = max(g[0][k], a[j][k]);
                    g[1][k] = min(g[1][k], a[j][k]);  
                    while (l[0] <= r[0] && g[0][k] >= g[0][que[0][r[0]]]) --r[0];
                    que[0][++r[0]] = k;
                    while (l[1] <= r[1] && g[1][k] <= g[1][que[1][r[1]]]) --r[1];
                    que[1][++r[1]] = k;
                    while (pos <= k && g[0][que[0][l[0]]] - g[1][que[1][l[1]]] > m) {
                        ++pos;
                        for (int o = 0; o < 2; ++o) {
                            while (l[o] <= r[o] && que[o][l[o]] < pos) ++l[o];
                        }
                    }
                    if (pos <= k) {
                        res = max(res, (j - i + 1) * (k - pos + 1));
                    }
                }
            }
        }
        printf("%d\n", res);
    }
    return 0;
}

G. Removing Stones

题意:
先定义一种游戏:
\(n\)堆石头,\(Bob\)每次可以选择两堆非空的各取走一个石头,如果最后能取光它就能获胜。
显然,这个游戏,如果\(n\)堆石头的总和是奇数,那么显然赢不了。
那么假如一条额外规则,如果总和是奇数,那么先去掉石头个数最小的那堆的一个石头。
现在询问有多少个区间\((l_i, r_i)\),使用这个区间内的石头进行游戏是必胜的。

思路:
首先注意到游戏必胜的条件是,区间的石头总数和大于等于两倍的区间最大值。
那么我们把区间的贡献都算在区间中拥有最大数量石头的那堆上,先处理出一个石头的管辖边界\([l, r]\),然后枚举一边,二分找另一边。
要枚举短的那边,这样复杂度是\(\mathcal{O}(nlog^2n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define N 300010
#define pii pair <int, int>
#define fi first
#define se second
int n, a[N];
pii b[N];
ll S[N];
 
struct Cartesian_Tree {
    struct node {
        int id, val, fa;
        int son[2];
        node() {}
        node (int id, int val, int fa) : id(id), val(val), fa(fa) {
            son[0] = son[1] = 0;
        }
        bool operator < (const node &other) const {
            return id < other.id;
        }
    }t[N];
    int root;
    void init() {
        t[0] = node(0, 1e9, 0);
    }
    void build(int n, int *a) {
        for (int i = 1; i <= n; ++i) {
            t[i] = node(i, a[i], 0);
        }
        for (int i = 1; i <= n; ++i) {
            int k = i - 1;
            while (t[k].val < t[i].val) {
                k = t[k].fa;
            }
            t[i].son[0] = t[k].son[1];
            t[k].son[1] = i;
            t[i].fa = k;
            t[t[i].son[0]].fa = i;
        }
        root = t[0].son[1];
    }
    int DFS(int u) {
        if (!u) return 0;
        int lsze = DFS(t[u].son[0]);
        int rsze = DFS(t[u].son[1]);
        b[t[u].id].fi = lsze;
        b[t[u].id].se = rsze;
        return lsze + rsze + 1;
    }
}CT;
 
int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        S[0] = 0;
        for (int i = 1; i <= n; ++i) scanf("%d", a + i), S[i] = S[i - 1] + a[i];
        CT.init();
        CT.build(n, a);
        CT.DFS(CT.root);
        ll res = 0;
        for (int i = 1; i <= n; ++i) {
            int l = i - b[i].fi, r = i + b[i].se;
        //  cout << i << " " << l << " " << r << endl;
            //枚举左边
            if (i - l <= r - i) {
                for (int j = l; j <= i; ++j) {
                    int ql = i, qr = r, pos = -1;
                    while (qr - ql >= 0) {
                        int mid = (ql + qr) >> 1;
                        if (S[mid] - S[j - 1] - a[i] >= a[i]) {
                            pos = mid;
                            qr = mid - 1;
                        } else {
                            ql = mid + 1;
                        }
                    }
                    if (pos != -1) {
                        res += r - pos + 1;
                    }
                }
            } else {
                for (int j = i; j <= r; ++j) {
                    int ql = l, qr = i, pos = -1;
                    while (qr - ql >= 0) {
                        int mid = (ql + qr) >> 1;
                        if (S[j] - S[mid - 1] - a[i] >= a[i]) {
                            pos = mid;
                            ql = mid + 1;
                        } else {
                            qr = mid - 1;
                        }
                    }
                    if (pos != -1) {
                        res += pos - l + 1;
                    }
                }
            }
        }
        printf("%lld\n", res);
    }
    return 0;
}

H. Magic Line

题意:
二维平面上有\(n\)个点,问能否画一条直线使得直线上没有点,并且直线两边的点数相同。

思路:
如果\(x\)坐标没有重复,那么排序后直接从中间画一条直线。
否则微微倾斜一个角度。

代码:

#include <bits/stdc++.h>
 
using namespace std;
 
#define N 1010
 
struct node {
    int x, y;
 
    node() {}
 
    node(int x, int y) : x(x), y(y) {}
 
    void input() {
        scanf("%d %d", &x, &y);
    }
 
    bool operator<(const node &other) const {
        if (y == other.y) {
            return x < other.x;
        } else {
            return y < other.y;
        }
    }
} a[N];
 
int n;
 
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            a[i].input();
        }
        sort(a + 1, a + 1 + n);
        int mid = n >> 1;
        if (a[mid].y == a[mid + 1].y) {
            printf("%d %d %d %d\n", a[mid].x - 10000000, a[mid].y + 1, a[mid + 1].x + 10000000, a[mid].y - 1);
        } else {
            if (a[mid].x > a[mid + 1].x) {
                printf("%d %d %d %d\n", -10000000, a[mid].y, 10000000, a[mid + 1].y);
            } else {
                printf("%d %d %d %d\n", -10000000, a[mid + 1].y, 10000000, a[mid].y);
            }
        }
    }
    return 0;
}

I. Median

题意:
\(n\)个数\(a_i\),现在令\(b_i\)等于\(\{a_i, a_{i + 1}, a_{i + 2}\}\)三个数中的中位数,现在给出\(b_1 \cdots b_{n - 2}\),问能否还原\(a_i\)

思路:
首先要考虑如果存在解,那么一定可以让\(a_i\)等于与它相关联的三个中位数。
因为如果不等于的话,那么\(a_i\)一定大于等于三个相关联的中位数,或者小于等于三个相关联的中位数,那么直接等于是没有问题的。
再考虑\(v[i][j](j \in [0, 2])\),第\(i\)个数中相关联的三个中位数的第\(j\)个(排序后的,其实不排序也是可以的)。
再考虑\(f[i][j][k] (j, k \in [0, 2])\),表示第\(i\)个数填\(v[i][j]\),第\(i - 1\)个数填\(v[i - 1][k]\),并且使得前\(i - 2\)个数都满足要求的情况下,当前方案是否能够成立。
然后枚举下一位转移即可,并且记录一下前驱,如果存在\(f[n][j][k]\)满足,那么直接找前驱即可。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 100010
int n, a[N], b[N];
int f[N][3][3], pre[N][3][3], v[N][3], s[3];
 
int med(int x, int y, int z) {
    s[0] = x, s[1] = y, s[2] = z;
    sort(s, s + 3);
    return s[1];
}
 
void get(int &x, int &y) {
    x = -1, y = -1;
    for (int i = 0; i < 3; ++i)
        for (int j = 0; j < 3; ++j)
            if (f[n][i][j]) {
                x = i;
                y = j;
                return;
            }
}
 
void solve() {
    int x, y; get(x, y);
    if (x == -1 || y == -1) {
        puts("-1");
        return;
    }
    for (int i = n; i >= 1; --i) {
        b[i] = v[i][x];
        x = pre[i][x][y];
        swap(x, y);
    }
    for (int i = 2; i < n; ++i) assert(med(b[i - 1], b[i], b[i + 1]) == a[i]);
    for (int i = 1; i <= n; ++i) printf("%d%c", b[i], " \n"[i == n]);
}
 
int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 2; i < n; ++i) scanf("%d", a + i);
        a[0] = a[1] = a[2];
        a[n] = a[n + 1] = a[n - 1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < 3; ++j)
                v[i][j] = a[i + j - 1];
            sort(v[i], v[i] + 3);
        }
        for (int i = 1; i <= n; ++i)
            for (int j = 0; j < 3; ++j)
                for (int k = 0; k < 3; ++k)
                    f[i][j][k] = 0;
        for (int i = 0; i < 3; ++i)
            for (int j = 0; j < 3; ++j)
                f[2][i][j] = 1;
        for (int i = 3; i <= n; ++i)
            for (int j = 0; j < 3; ++j)
                for (int k = 0; k < 3; ++k)
                    for (int l = 0; l < 3; ++l) {
                        if (!f[i - 1][k][l]) continue;
                        if (med(v[i - 2][l], v[i - 1][k], v[i][j]) != a[i - 1]) continue;
                        f[i][j][k] = 1;
                        pre[i][j][k] = l;
                    }
        solve();   
    }
    return 0;
}

J. LRU management

题意:
模拟\(LRU\)操作。

  • 添加一个\(block\)到缓存中,如果已经存在,将它移到末尾
  • 询问一个\(block\)是否在缓存中,如果存在,则输出它的前驱或者它本身或者它的后继的数据。

思路:
\(map\)第一关键字存\(string\),第二关键字存链表的迭代器。\(mp.find\)好像要比\(mp.count\)要快?
这种题还是要仔细读题,想清楚再上手。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define endl "\n"
#define psi pair <ll, int>
#define fi first
#define se second
#define iter list<psi>::iterator
int q, m;
list <psi> lis;  
unordered_map <ll, iter> mp;
  
ll change(char str[]){
    ll r;
    sscanf(str,"%lld",&r);
    return r+10000000000ll*(strlen(str)-1);
}
 
void No() {
    puts("Invalid");
}
 
int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &q, &m);
        mp.clear();
        lis.clear(); 
        int op, v; char ts[20]; ll s;
        while (q--) {
            scanf("%d%s%d", &op, ts, &v);
            s = change(ts);
            if (op == 0) {
                if (mp.find(s) == mp.end()) {
                    lis.push_back(psi(s, v));
                    mp[s] = lis.end(); --mp[s];
                    printf("%d\n", v);
                } else {
                    v = (*mp[s]).se;
                    printf("%d\n", v);
                    lis.erase(mp[s]);
                    lis.push_back(psi(s, v));
                    mp[s] = lis.end(); --mp[s];
                }
                if ((int)lis.size() > m) {
                    mp.erase(lis.front().fi);
                    lis.pop_front(); 
                }
            } else {
                if (mp.find(s) == mp.end()) No();
                else {
                    iter pos = mp[s];
                    if (v == 0) printf("%d\n", (*pos).se);
                    else if (v == -1) {
                        if (pos == lis.begin()) No();
                        else {
                            printf("%d\n", (*--pos).se);
                        }
                    } else {
                        if (pos == --lis.end()) No();
                        else {
                            printf("%d\n", (*++pos).se);
                        }
                    }
                }
            }
        }
    }
    return 0;
}
posted @ 2019-07-26 07:51  Dup4  阅读(331)  评论(0编辑  收藏  举报