BZOJ Lydsy5月月赛 ADG题解

题目链接

BZOJ5月月赛

题解

好弱啊QAQ只写出三题

A

判断多干个数乘积是否是某个数的倍数有很多方法,比较常用的是取模,但这里并不适用,因为模数不定
会发现数都比较小,所以我们可以考虑分解质因子,查找一下区间各个质因子数是否符合要求
用主席树维护即可
由于\(10^5\)以内不同质因子数最多的也就是\(6\)个,预处理一下质因子,可以看做一个常数
复杂度是\(O(n\sqrt{n} + nlogn)\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 100005,maxm = 10000005,N = 100000,INF = 1000000000;
inline int read(){
	int out = 0,flag = 1; char c = getchar();
	while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
	while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
	return out * flag;
}
int fac[maxn][20],num[maxn][20],fi[maxn],M;
int p[maxn],pi,isn[maxn];
void init(){
	for (int i = 2; i <= N; i++){
		if (!isn[i]) p[++pi] = i;
		for (int j = 1; j <= pi && i * p[j] <= N; j++){
			isn[i * p[j]] = true;
			if (i % p[j] == 0) break;
		}
	}
	for (int i = 2; i <= N; i++){
		int x = i,t;
		for (int j = 1; j <= pi && p[j] * p[j] <= x; j++){
			if (x % p[j] == 0){
				t = ++fi[i];
				fac[i][t] = j;
				while (x % p[j] == 0) num[i][t]++,x /= p[j];
			}
		}
		if (x - 1){
			fac[i][++fi[i]] = lower_bound(p + 1,p + 1 + pi,x) - p;
			num[i][fi[i]] = 1;
		}
	}
	M = pi;
}
int sum[maxm],ls[maxm],rs[maxm],rt[maxn],A[maxn],n,m,cnt;
void add(int& u,int v,int l,int r,int pos,int w){
	sum[u = ++cnt] = sum[v] + w;
	ls[u] = ls[v]; rs[u] = rs[v];
	if (l == r) return;
	int mid = l + r >> 1;
	if (mid >= pos) add(ls[u],ls[v],l,mid,pos,w);
	else add(rs[u],rs[v],mid + 1,r,pos,w);
}
int query(int u,int v,int l,int r,int pos){
	if (!u) return 0;
	if (l == r) return sum[u] - sum[v];
	int mid = l + r >> 1;
	if (mid >= pos) return query(ls[u],ls[v],l,mid,pos);
	return query(rs[u],rs[v],mid + 1,r,pos);
}
int main(){
	init();
	int T = read(),x,l,r,flag;
	while (T--){
		cnt = 0;
		n = read(); m = read();
		REP(i,n){
			x = A[i] = read(); rt[i] = rt[i - 1];
			for (int j = 1; j <= fi[x]; j++)
				add(rt[i],rt[i],1,M,fac[x][j],num[x][j]);
		}
		while (m--){
			l = read(); r = read(); x = read(); flag = true;
			for (int i = 1; i <= fi[x]; i++)
				if (query(rt[r],rt[l - 1],1,M,fac[x][i]) < num[x][i]){
					flag = false; break;
				}
			if (!flag) puts("No");
			else puts("Yes");
		}
	}
	return 0;
}

D

D是树上两点间的询问,考虑使用树上权值主席树
由于只考虑奇偶性,所以我们覆给每个位置\(1\)\(2\),修改就翻转一下
如何查询?
拿出\(u\)\(v\)所对应的两棵树,我们需要快速合并树上信息得出第一个偶数位置
容易知道当两个位置奇偶性不同时相加起来才是奇数,所以我们只需要找到两棵树第一个相同的位置
权值不同不好比较,那我们就再维护一个值,与原来的值相反,这样转化成了查找第一个不同的位置
我们只需快速比较两个区间是否相同,使用\(hash\)即可
两个树实际上对应两条到根的链,相交的部分除了\(lca\)处都应减去,由于只考虑奇偶性,所以对答案没有影响,所以只用把\(lca\)处的值修改一下即可进行比较
复杂度\(O(nlogn)\),还要优化一下常数,比如只建一次树,\(RMQ\)\(lca\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (register int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (register int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
#define ULL unsigned long long int
using namespace std;
const int maxn = 200005,maxm = 13000005,INF = 1000000000;
inline int read(){
	int out = 0,flag = 1; char c = getchar();
	while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
	while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
	return out * flag;
}
int h[maxn],ne = 1;
struct EDGE{int to,nxt;}ed[maxn << 1];
inline void build(int u,int v){
	ed[++ne] = (EDGE){v,h[u]}; h[u] = ne;
	ed[++ne] = (EDGE){u,h[v]}; h[v] = ne;
}
ULL pw[maxn],val[maxm],vv[maxm];
int A[maxn],fa[maxn],dep[maxn],dfn[maxn],mn[maxn << 1][19],Log[maxn << 1],bin[50],tot;
int rt[maxn],ls[maxm],rs[maxm],cnt,n,m,N = 200001,typ,pos,V;
inline void upd(int u,int l,int r){
	val[u] = val[ls[u]] * pw[r - (l + r >> 1)] + val[rs[u]];
	vv[u] = vv[ls[u]] * pw[r - (l + r >> 1)] + vv[rs[u]];
}
void build(int& u,int l,int r){
	u = ++cnt;
	if (l == r){val[u] = 1; vv[u] = 2; return;}
	int mid = l + r >> 1;
	build(ls[u],l,mid);
	build(rs[u],mid + 1,r);
	upd(u,l,r);
}
void modify(int& u,int v,int l,int r){
	u = ++cnt; ls[u] = ls[v]; rs[u] = rs[v];
	if (l == r){val[u] = val[v] == 1 ? 2 : 1; vv[u] = vv[v] == 1 ? 2 : 1; return;}
	int mid = l + r >> 1;
	if (mid >= pos) modify(ls[u],ls[v],l,mid);
	else modify(rs[u],rs[v],mid + 1,r);
	upd(u,l,r);
}
int query(int u,int v,int l,int r){
	if (l == r) return l;
	int mid = l + r >> 1;
	ULL t1 = val[ls[u]],t2 = vv[ls[v]];
	if (pos >= l && pos <= mid){
		if (V == 1) t1 += pw[mid - pos];
		else t1 -= pw[mid - pos];
	}
	if (t1 != t2) return query(ls[u],ls[v],l,mid);
	return query(rs[u],rs[v],mid + 1,r);
}
inline int ask(int u){
	int l = 1,r = N,mid;
	while (l < r){
		mid = l + r >> 1;
		if (mid >= pos) r = mid,u = ls[u];
		else l = mid + 1,u = rs[u];
	}
	return val[u];
}
void dfs(int u){
	pos = A[u]; mn[++tot][0] = u; dfn[u] = tot;
	modify(rt[u],rt[fa[u]],1,N);
	Redge(u) if ((to = ed[k].to) != fa[u]){
		fa[to] = u; dep[to] = dep[u] + 1;
		dfs(to);
		mn[++tot][0] = u;
	}
}
inline int lca(int u,int v){
	int l = dfn[u],r = dfn[v];
	if (l > r) swap(l,r);
	int t = Log[r - l + 1];
	return dep[mn[l][t]] < dep[mn[r - bin[t] + 1][t]] ? mn[l][t] : mn[r - bin[t] + 1][t];
}
/*void print(int u,int l,int r){
    if (l == r){
        printf("%lld ",val[u]);
        return;
    }
    int mid = l + r >> 1;
    print(ls[u],l,mid);
    if (6 > mid)print(rs[u],mid + 1,r);
}*/
int main(){
	bin[0] = 1; for (int i = 1; i <= 25; i++) bin[i] = bin[i - 1] << 1;
	Log[0] = -1; for (register int i = 1; i < (maxn << 1); i++) Log[i] = Log[i >> 1] + 1;
	pw[0] = 1; for (register int i = 1; i < maxn; i++) pw[i] = pw[i - 1] * 5;
	build(rt[0],1,N);
	int tmp = cnt;
	int T = read();
	while (T--){
		n = read(); m = read();
		REP(i,n) h[i] = 0;
		tot = 0; cnt = tmp; ne = 1;
		REP(i,n) A[i] = read();
		for (register int i = 1; i < n; i++) build(read(),read());
		dfs(1);
		for (register int j = 1; j <= 18; j++)
			for (register int i = 1; i <= tot; i++){
				if (i + bin[j] - 1 > tot) break;
				mn[i][j] = dep[mn[i][j - 1]] < dep[mn[i + bin[j - 1]][j - 1]] ? mn[i][j - 1] : mn[i + bin[j - 1]][j - 1];
			}
		register int u,v,o;
		while (m--){
			u = read(); v = read(); o = lca(u,v);
			//printf("lca(%d,%d) = %d\n",u,v,o);
			//print(rt[u],1,N); puts("");
			//print(rt[v],1,N); puts("");
			pos = A[o];
			V = ask(rt[u]);
			printf("%d\n",query(rt[u],rt[v],1,N));
		}
	}
	return 0;
}

G

一眼看过去以为是裸的后缀数组,仔细一看原来串那么长
但是思想是相通的
先讲讲如果是一般的串怎么做
我们只需要写出一个\(O(1)\)\(cmp\)函数即可进行排序
我们知道比较两个字符串实质是比较第一个不同的字符【串长不够特殊考虑】
所以我们只需要\(O(1)\)求出两个位置的\(lcp\)即可
这是后缀数组拿手的地方

然而换到了这道题,我们同样可以通过求\(lcp\)来进行比较,但不是用后缀数组
我们先压缩一下,保证任意相邻两段字符不同
我们发现\(m\)很小,可以\(O(m^2)\)dp求出任意两段开头位置开始的\(lcp\),记为\(f[i][j]\)
我们对于任意两个串,对于段开头前面的部分我们可以直接判断是否相同,如果它们字符都不一样当然不同,如果字符一样就看到该段末尾的距离,由于相邻段字符是不一样的,如果距离不同,那么到段的结尾后自然就不同了,如果距离还相同,那么就可以加上\(f[i][j]\),就是\(lcp\)
综上可以\(O(nlogn)\)预处理串的位置,\(O(m^2)\)dp,\(O(nlog^2n)\)排序
总复杂度\(O(nlog^2n + m^2)\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 300005,maxm = 2005,INF = 1000000000;
inline LL read(){
	LL out = 0,flag = 1; char c = getchar();
	while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
	while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
	return out * flag;
}
char s[maxm];
int n,m,id[maxn];
LL num[maxm],ll[maxn],len[maxn],l[maxn],lcp[maxm][maxm];
bool cmp(const int& x,const int& y){
	LL u = l[x],v = l[y],L;
	if (s[u] != s[v]) return s[u] < s[v];
	else {
		LL l1 = num[u] - ll[x] + 1,l2 = num[v] - ll[y] + 1;
		if (l1 != l2) L = min(l1,l2);
		else L = l1 + lcp[u + 1][v + 1];
	}
	L = min(L,min(len[x],len[y]));
	//printf("%d-%d  lcp = %lld\n",x,y,L);
	if (L == len[x] && L == len[y]) return x < y;
	if (L == len[x]) return true;
	if (L == len[y]) return false;
	int p1 = lower_bound(num + 1,num + 1 + m,ll[x] + L) - num;
	int p2 = lower_bound(num + 1,num + 1 + m,ll[y] + L) - num;
	return s[p1] < s[p2];
}
int main(){
	int T = read(); char c;
	LL L,R;
	while (T--){
		n = read(); m = read();
		for (int i = 1; i <= m; i++){
			c = getchar(); while (!isalpha(c)) c = getchar();
			num[i] = read();
			if (i > 1 && c == s[i - 1]){
				num[i - 1] += num[i];
				i--; m--;
			}
			else s[i] = c;
		}
		REP(i,m) lcp[i][m + 1] = lcp[m + 1][i] = 0;
		for (int i = m; i; i--)
			for (int j = m; j; j--){
				if (s[i] != s[j]) lcp[i][j] = 0;
				else if (num[i] == num[j]) lcp[i][j] = num[i] + lcp[i + 1][j + 1];
				else lcp[i][j] = min(num[i],num[j]);
				//printf("lcp(%d,%d) = %lld\n",i,j,lcp[i][j]);
			}
		for (int i = 1; i <= m; i++) num[i] += num[i - 1];
		for (int i = 1; i <= n; i++){
			L = read(); R = read(); id[i] = i;
			len[i] = R - L + 1;
			l[i] = lower_bound(num + 1,num + 1 + m,L) - num;
			ll[i] = L;
		}
		sort(id + 1,id + 1 + n,cmp);
		for (int i = 1; i <= n; i++){
			printf("%d",id[i]);
			if (i < n) putchar(' ');
		}
		if (T) puts("");
	}
	return 0;
}

posted @ 2018-05-27 18:35  Mychael  阅读(366)  评论(0编辑  收藏  举报