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

Contest Info


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

Solutions


A. meeting

题意:
给出一棵\(n\)个点的树,现在有\(k\)个人在不同的位置上,要求让他们集合到一点,定义代价为每个人走过的边数的最大值,问选择一点使得代价最少,输出最少代价。

思路:
考虑题意就是选一个根,使得这\(k\)个人所在位置的最大深度最小。
那么令:

  • \(f[i]\)表示以\(i\)为根的子树中,最大深度为多少。
  • \(g[i]\)表示除了\(i\)的子树中,其他点到\(i\)的最大距离多少。

然后两遍\(DFS\)即可。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 100010
int n, k;
vector <vector<int>> G;
int f[N][2], pos[N][2], g[N], vis[N], res;
 
int deep[N], fa[N];
void DFS(int u) {
    if (vis[u]) {
        f[u][0] = deep[u];
        pos[u][0] = u;
        f[u][1] = -1;
        pos[u][1] = -1;
    } else {
        f[u][0] = -1;
        f[u][1] = -1;
        pos[u][0] = -1;
        pos[u][1] = -1;
    }
    for (auto v : G[u]) if (v != fa[u]) {
        deep[v] = deep[u] + 1;
        fa[v] = u;
        DFS(v);
        if (f[v][0] > f[u][0]) {
            f[u][1] = f[u][0];
            pos[u][1] = pos[u][0];
            f[u][0] = f[v][0];
            pos[u][0] = v;
        } else if (f[v][0] > f[u][1]) {
            f[u][1] = f[v][0];
            pos[u][1] = v; 
        }
    }
}
 
void DFS2(int u) {
    if (u == 1) {
        g[u] = -1; 
    } else {
        g[u] = -1;
        int pre = fa[u];
        if (g[pre] != -1) g[u] = g[pre] + 1;
        if (f[pre][0] != -1 && pos[pre][0] != u) {
            g[u] = max(g[u], 1 + f[pre][0] - deep[pre]);
        }
        if (f[pre][1] != -1 && pos[pre][1] != u) {
            g[u] = max(g[u], 1 + f[pre][1] - deep[pre]);
        }
    }
    res = min(res, max(f[u][0] - deep[u], g[u]));
    for (auto v : G[u]) if (v != fa[u]) {
        DFS2(v);
    }
}
 
int main() {
    while (scanf("%d%d", &n, &k) != EOF) {
        G.clear(); G.resize(n + 1);
        for (int i = 1, u, v; i < n; ++i) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        memset(vis, 0, sizeof vis);
        for (int i = 1, x; i <= k; ++i) {
            scanf("%d", &x);
            vis[x] = 1;
        }
        fa[1] = 0; deep[1] = 0;
        res = 1e9;
        DFS(1);
        DFS2(1);
    //  for (int i = 1; i <= n; ++i) printf("%d %d %d %d\n", i, f[i][0] - deep[i], f[i][1], g[i]);
        printf("%d\n", res);
    }
    return 0;
}

B.xor

题意:
\(n\)个集合,每个集合的数的个数不超过\(32\)个,现在每次询问\((l, r, x)\),问\([l, r]\)范围内的每个集合是否都能选择若干个数异或起来来表达\(x\)

思路:
其实问题等价于\([l, r]\)范围的每个集合的线性基的交能否表达\(x\),这个时间复杂度是\(\mathcal{O}(32^2n + 32^2qlogn)\)
或者直接暴力\(check\)每个线性基能否表达\(x\),这个复杂度是\(\mathcal{O}(32qn)\)
那么我们考虑结合一下,我们建一棵线段树,需要\(O(n)\)次求交,然后查询的时候在分出的\(logn\)个区间中分别查询,那么复杂度为\(\mathcal{O}(32^2n + 32qlogn)\)

代码:

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

#define N 50010
#define u32 unsigned int
int n, q;
u32 a[N][33];

struct Base {
	static const int L = 32;
	u32 a[L];
	Base() {
		memset(a, 0, sizeof a);
	}
	u32& operator [](int x) {
		return a[x];
	}
	u32 operator [](int x) const {
		return a[x];
	}
	void insert(u32 x) {
		for (int i = L - 1; i >= 0; --i) {
			if ((x >> i) & 1) {
				if (a[i]) x ^= a[i];
				else {
					a[i] = x;
					break;
				}
			}
		}
	}
	bool check(u32 x) {
		for (int i = L - 1; i >= 0; --i) {
			if ((x >> i) & 1) {
				if (a[i]) x ^= a[i];
				else return 0;
			}
		}
		return 1;
	}
	//线性基求交
	friend Base intersection(const Base &a, const Base &b) {
		Base ans = Base(), c = b, d = b;
		for (int i = 0; i <= L - 1; ++i) {
			u32 x = a[i];
			if (!x) continue;
			int j = i; u32 T = 0;
			for (; j >= 0; --j) {
				if ((x >> j) & 1) {
					if (c[j]) {
						x ^= c[j];
						T ^= d[j];   
					} else {
						break;
					}
				}
			}
			if (!x) ans[i] = T;
			else {
				c[j] = x;
				d[j] = T;
			}
		}
		return ans;
	}
};

struct SEG {
	Base t[N << 2];
	void build(int id, int l, int r) {
		if (l == r) {
			t[id] = Base(); 
			for (int j = 1; j <= a[l][0]; ++j) {
				t[id].insert(a[l][j]);
			}
			return;
		}
		int mid = (l + r) >> 1;
		build(id << 1, l, mid);
		build(id << 1 | 1, mid + 1, r);
		t[id] = intersection(t[id << 1], t[id << 1 | 1]);
	}
	bool query(int id, int l, int r, int ql, int qr, int x) {
		if (l >= ql && r <= qr) {
			return t[id].check(x);
		}
		int mid = (l + r) >> 1;
		if (ql <= mid && !query(id << 1, l, mid, ql, qr, x)) return 0;
		if (qr > mid && !query(id << 1 | 1, mid + 1, r, ql, qr, x)) return 0;
		return 1;
	}
}seg;

int main() {
	while (scanf("%d%d", &n, &q) != EOF) {
		for (int i = 1; i <= n; ++i) {
			scanf("%u", a[i]);
			for (int j = 1; j <= a[i][0]; ++j) {
				scanf("%u", a[i] + j);
			}
		}
		seg.build(1, 1, n);
		int l, r, x;
		while (q--) {
			scanf("%d%d%d", &l, &r, &x);
			puts(seg.query(1, 1, n, l, r, x) ? "YES" : "NO");
		}
	}
	return 0;
}

C. sequence

题意:
给出两个序列\(a_i\), \(b_i\),求下列式子:

\[\begin{eqnarray*} max_{1 \leq l \leq r \leq n} \{min(a_{l \cdots r}) \times sum(b_{l \cdots r})\} \end{eqnarray*} \]

思路:
考虑处理出每个点\(i\)的管辖范围\(l, r\),那么分成两段\([l, i - 1]\)\([i, r]\)
然后查询左段的前缀和最大最小值,右段的前缀和最大最小值,更新答案即可。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define pii pair <int, int>
#define fi first
#define se second
#define ll long long
#define INFLL 0x3f3f3f3f3f3f3f3f
#define N 3000010
int n, a[N];
ll b[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;
        }
    }t[N];
    int root;
    pii b[N];
    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] = pii(lsze, rsze);
        return lsze + rsze + 1;
    }
}CT;
 
struct SEG {
    struct node {
        ll Max, Min;
        node() {
            Max = -INFLL;
            Min = INFLL;
        }
        node operator + (const node &other) const {
            node res = node();
            res.Max = max(Max, other.Max);
            res.Min = min(Min, other.Min);
            return res;
        }
    }t[N << 2], resl, resr;
    void build(int id, int l, int r) {
        if (l == r) {
            t[id] = node();
            t[id].Max = t[id].Min = b[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(id << 1, l, mid);
        build(id << 1 | 1, mid + 1, r);
        t[id] = t[id << 1] + t[id << 1 | 1];
    }
    node query(int id, int l, int r, int ql, int qr) {
        if (l >= ql && r <= qr) {
            return t[id];
        }
        int mid = (l + r) >> 1;
        node res = node();
        if (ql <= mid) res = res + query(id << 1, l, mid, ql, qr);
        if (qr > mid) res = res + query(id << 1 | 1, mid + 1, r, ql, qr);
        return res;
    }
}seg;
 
namespace IO
{
    const int S=(1<<20)+5;
    //Input Correlation
    char buf[S],*H,*T;
    inline char Get()
    {
        if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
        if(H==T) return -1;return *H++;
    }
    inline int read()
    {
        int x=0,fg=1;char c=Get();
        while(!isdigit(c)&&c!='-') c=Get();
        if(c=='-') fg=-1,c=Get();
        while(isdigit(c)) x=x*10+c-'0',c=Get();
        return x*fg;
    }
    inline void reads(char *s)
    {
        char c=Get();int tot=0;
        while(c<'a'||c>'z') c=Get();
        while(c>='a'&&c<='z') s[++tot]=c,c=Get();
        s[++tot]='\0';
    }
    //Output Correlation
    char obuf[S],*oS=obuf,*oT=oS+S-1,c,qu[55];int qr;
    inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
    inline void putc(char x){*oS++ =x;if(oS==oT) flush();}
    template <class I>inline void print(I x)
    {
        if(!x) putc('0');
        if(x<0) putc('-'),x=-x;
        while(x) qu[++qr]=x%10+'0',x/=10;
        while(qr) putc(qu[qr--]);
        putc('\n');
    }
    inline void prints(const char *s)
    {
        int len=strlen(s);
        for(int i=0;i<len;i++) putc(s[i]);
        putc('\n');
    }
    inline void printd(int d,double x)
    {
        long long t=(long long)floor(x);
        print(t);putc('.');x-=t;
        while(d--)
        {
            double y=x*10;x*=10;
            int c=(int)floor(y);
            putc(c+'0');x-=floor(y);
        }
    }
}using namespace IO;
 
int main() {
    n = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    b[0] = 0;
    for (int i = 1; i <= n; ++i) {
        b[i] = read();
        b[i] += b[i - 1];
    }
    CT.init();
    CT.build(n, a);
    CT.DFS(CT.root);
    ll res = -INFLL;
    seg.build(1, 0, n);
    for (int i = 1; i <= n; ++i) {
        int l = i - CT.b[i].fi, r = i + CT.b[i].se;
        seg.resl = seg.query(1, 0, n, l - 1, i - 1);
        seg.resr = seg.query(1, 0, n, i, r);
        res = max(res, 1ll * a[i] * (seg.resr.Max - seg.resl.Min));
        res = max(res, 1ll * a[i] * (seg.resr.Min - seg.resl.Max));
    }
    printf("%lld\n", res);
    return 0;
}

D. triples I

题意:
给出一个数\(a\),要求选择尽量少的数,使得这些数是\(3\)的倍数,并且它们\(or\)起来等于\(a\)
题目保证有解。

思路:

  • 考虑二进制位当中每一位都是模\(3\)\(2\),或者模\(3\)\(1\)
  • 如果\(a \equiv 0 \bmod 3\),那么直接输出\(a\)
  • 那么考虑\(a\)中如果所有二进制位模\(3\)的余数都相同,那么直接取三个位的\(or\)值作为\(A\),然后根据\(a\)\(3\)的余数,考虑\(a\)中要去掉一位或者两位。
  • 再考虑\(a\)中同时有模\(3\)\(1\)\(2\)的位,那么对于\(A\),直接各取一位,然后根据\(a\)\(3\)的结果判断去掉哪一位。

代码:

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

#define ll long long
ll a, vec[110]; int sze;

ll f(ll x, int p) {
	ll base = 1;
	while (x) {
		if (x % 2) {
			if (base % 3 == p) {
				return base; 
			}
		}
		x >>= 1;
		base <<= 1;
	}
	return -1;
}

void solve(ll x) {
	sze = 0;
	ll base = 1;
	ll t = x;
	while (x) {
		if (x % 2) {
			vec[sze++] = base;
		}
		x >>= 1;
		base <<= 1;
	}	
	ll A = vec[0] | vec[1] | vec[2];
	ll B = t ^ vec[0];
	if ((t % 3) != (vec[0] % 3)) {
		B ^= vec[1];
	}
	printf("2 %lld %lld\n", A, B);
}

bool same(ll x) {
	sze = 0;
	ll base = 1;
	while (x) {
		if (x % 2) {
			vec[sze++] = base;
		}
		base <<= 1;
		x >>= 1;
	}
	for (int i = 1; i < sze; ++i) {
		if ((vec[i] % 3) != (vec[i - 1] % 3)) {
			return 0;
		}
	}
	return 1;
}

int main() {
	int T; scanf("%d", &T);
	while (T--) {
		scanf("%lld", &a);
		if (a % 3 == 0) {
			printf("1 %lld\n", a);
		} else if (same(a)) {
			solve(a);
		} else if (a % 3 == 1) {
			ll x = f(a, 1);	
			if (x != -1) {
				ll y = a ^ x;
				x ^= f(a, 2);
				printf("2 %lld %lld\n", x, y);
			} 
		} else if (a % 3 == 2) {
			ll x = f(a, 2);
			if (x != -1) {
				ll y = a ^ x;
				x ^= f(a, 1);
				printf("2 %lld %lld\n", x, y);
			} 
		}
	}
	return 0;
}

I.string

题意:
给出一个字符串\(s\),定义两个子串不等价当且仅当两个子串\(a, b\)不同并且\(a\)不等于\(b\)的翻转,问从\(s\)中选出尽量多的子串,使得两两之间不等价。

思路:
考虑将\(s\)\(s\)的翻转用'$'连接起来,那么首先两个串的子串个数相同,那么去掉了相同的部分,剩下的是等数量的不同部分\(p\)
那么再考虑回文串带来的影响,我们发现对于回文串多减了一次,那么把所有本质不同的回文串都拉出来,假设数量为\(q\)
那么答案就是\(\frac{p + q}{2}\)

代码:

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

#define ll long long
#define N 500010
int n;
char s[N], t[N];
struct DA {
	int t1[N], t2[N], c[N];
	int sa[N];
	int Rank[N];
	int height[N];
	int str[N];
	int n, m;
	void init(char *s, int m, int n) {
		this->m = m;
		this->n = n;
		for (int i = 0; i < n; ++i) str[i] = s[i];
		str[n] = 0;
	}
	bool cmp(int *r, int a, int b, int l) {
		return r[a] == r[b] && r[a + l] == r[b + l];
	}
	void work() {
		++n;
		int i, j, p, *x = t1, *y = t2;
		for (i = 0; i < m; ++i) c[i] = 0;
		for (i = 0; i < n; ++i) c[x[i] = str[i]]++;
		for (i = 1; i < m; ++i) c[i] += c[i - 1];
		for (i = n - 1; i >= 0; --i) sa[--c[x[i]]] = i;
		for (j = 1; j <= n; j <<= 1) {
			p = 0;
			for (i = n - j; i < n; ++i) {
				y[p++] = i;
			}
			for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
			for (i = 0; i < m; ++i) c[i] = 0;
			for (i = 0; i < n; ++i) c[x[y[i]]]++;
			for (i = 1; i < m; ++i) c[i] += c[i - 1];
			for (i = n - 1; i >= 0; --i) sa[--c[x[y[i]]]] = y[i];
			swap(x, y);
			p = 1; x[sa[0]] = 0;
			for (i = 1; i < n; ++i) {
				x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
			}
			if (p >= n) break;
			m = p;
		}
		int k = 0;
		--n;
		for (i = 0; i <= n; ++i) Rank[sa[i]] = i;
		//build height
		for (i = 0; i < n; ++i) {
			if (k) --k;
			j = sa[Rank[i] - 1];
			while (str[i + k] == str[j + k]) ++k;
			height[Rank[i]] = k; 
		}
	}
}da;

#define ALP 26
struct PAM{         
    int Next[N][ALP];
    int fail[N];    
    int cnt[N];     
    int num[N];   
    int len[N];  
    int s[N];       
    int last;       
    int n;         
    int p;         
 
    int newnode(int w){ 
        for(int i=0;i<ALP;i++)
            Next[p][i] = 0;
        cnt[p] = 0;
        num[p] = 0;
        len[p] = w;
        return p++;
    }
    void init(){
        p = 0;
        newnode(0);
        newnode(-1);
        last = 0;
        n = 0;
        s[n] = -1; 		
        fail[0] = 1;
    }
    int get_fail(int x){
        while(s[n-len[x]-1] != s[n]) x = fail[x];
        return x;
    }
    bool add(int c){
		bool F = 0;
        c -= 'a';
        s[++n] = c;
		int cur = get_fail(last);
		if(!Next[cur][c]){
            int now = newnode(len[cur]+2);//新建节点
            fail[now] = Next[get_fail(fail[cur])][c];
            Next[cur][c] = now;
            num[now] = num[fail[now]] + 1;
			F = 1;
		}
        last = Next[cur][c];
        cnt[last]++;
		return F;
    }
    void count(){
        for(int i=p-1;i>=0;i--)
            cnt[fail[i]] += cnt[i];
    }
}pam; 

int main() {
	while (scanf("%s", s) != EOF) {
		int len = strlen(s);
		n = 0;
		for (int i = 0; i < len; ++i) t[n++] = s[i];
		t[n++] = '$';
		for (int i = len - 1; i >= 0; --i) t[n++] = s[i];
		da.init(t, 128, n);
		da.work();
		t[n] = 0;
		ll res = 0;
		for (int i = 1; i <= n; ++i) {
			if (t[da.sa[i]] != '$') {
				if (da.sa[i] > len) {
					res += n - da.sa[i] - da.height[i];
				} else {
					res += len - da.sa[i] - da.height[i];
				}
			}
		}
		pam.init();
		for (int i = 0; i < len; ++i) pam.add(s[i]);
		printf("%lld\n", (res + pam.p - 2) / 2);
	}
	return 0;
}

J. free

题意:
给出\(n\)个点\(m\)条边的图,求最多去掉\(m\)条边的权值从\(S \rightarrow T\)的最短路。

思路:
考虑\(f[i][j]\)表示终点为\(i\)已经去掉了\(j\)条边的代价的最短路。

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define N 1010
#define INF 0x3f3f3f3f
int n, m, S, T, K;
struct Graph {
    struct node {
        int to, nx, w;
        node() {}
        node (int to, int nx, int w) : to(to), nx(nx), w(w) {}
    }a[N << 1];
    int head[N], pos;
    void init() {
        pos = 0;
        memset(head, -1, sizeof head);
    }
    void add(int u, int v, int w) {
        a[pos] = node(v, head[u], w); head[u] = pos++;
        a[pos] = node(u, head[v], w); head[v] = pos++;
    }
}G;
#define erp(u) for (int it = G.head[u], v = G.a[it].to, w = G.a[it].w; ~it; it = G.a[it].nx, v = G.a[it].to, w = G.a[it].w)
 
struct node {
    int u, w, k;
    node() {}
    node(int u, int w, int k) : u(u), w(w), k(k) {}
    bool operator < (const node &other) const {
        return w > other.w;
    }
};
 
int dist[N][N], used[N][N];
void Dijkstra() {
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= K; ++j) {
            dist[i][j] = INF;
            used[i][j] = 0;
        }
    dist[S][0] = 0;
    priority_queue <node> pq;
    pq.push(node(S, dist[S][0], 0));
    while (!pq.empty()) {
        int u = pq.top().u, k = pq.top().k; pq.pop();
        if (used[u][k]) continue;
        if (u == T) break;
        used[u][k] = 1;
        erp(u) if (dist[v][k] > dist[u][k] + w) {
            dist[v][k] = dist[u][k] + w;
            pq.push(node(v, dist[v][k], k));
        }
        if (k < K) {
            erp(u) if (dist[v][k + 1] > dist[u][k]) {
                dist[v][k + 1] = dist[u][k];
                pq.push(node(v, dist[v][k + 1], k + 1));
            }
        }
    }
    int res = INF;
    for (int i = 0; i <= K; ++i) res = min(res, dist[T][i]);
    printf("%d\n", res);
}
 
int main() {
    while (scanf("%d%d%d%d%d", &n, &m, &S, &T, &K) != EOF) {
        G.init();
        for (int i = 1, u, v, w; i <= m; ++i) {
            scanf("%d%d%d", &u, &v, &w);
            if (u == v) continue;
            G.add(u, v, w);
        }
        Dijkstra();
    }
    return 0;
}

K. number

题意:
给出一个字符串,问里面有多少个子串它的数值含义是\(300\)的倍数,允许前导\(0\)

思路:
考虑一个子串是\(300\)的倍数,那么只需要让它既是\(100\)的倍数,又是\(3\)的倍数即可。

  • \(100\)的倍数即末尾两位是\(0\)
  • \(3\)的倍数有一个特性,即只要它的数位之和加起来是\(3\)的倍数即可,那么直接\(dp\)转移即可。
  • 再特判一个\(0\)的情况

代码:

#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define N 100010
char s[N];
ll f[N][3];
 
int main() {
    while (scanf("%s", s + 1) != EOF) {
        memset(f, 0, sizeof f);
        int len = strlen(s + 1);
        int cnt = 0;
        ll res = 0;
        for (int i = 1; i <= len; ++i) {
            int num = (s[i] - '0') % 3;
            ++f[i][num];
            for (int j = 0; j < 3; ++j) {
                f[i][j] += f[i - 1][(j - num + 3) % 3]; 
            }
            cnt += (s[i] == '0');
            if (i >= 2 && s[i] == '0' && s[i - 1] == '0') {
                res += f[i - 2][0] + 1;
            }
        }
        printf("%lld\n", res + cnt);
    }
    return 0;
}
posted @ 2019-07-28 08:26  Dup4  阅读(291)  评论(0编辑  收藏  举报