清北灵堂2022.1.24模拟赛
A
虽然题目背景介绍了LCT,不过这道题完全不需要用LCT
我们可以提前把最后的森林建出来
每个询问就是查询到某个祖先的路径权值异或和,用并查集维护此时所在树的根节点,路径异或和可用带权并查集或预处理好每个点到最后的根节点的路径,两个点异或即可
时间复杂度\(O(n+m)\)
点击查看代码
#include <cstdio>
const int N = 500005;
int w[N], fa[N], p[N];
inline int find(int x) {
	if (x == fa[x]) return fa[x];
	int t = find(fa[x]);
	w[x] = w[x] ^ w[fa[x]];
	return fa[x] = t;
}
int main()
{
	int n, m; scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &p[i]), fa[i] = i;
	for (int i = 1; i <= m; i++) {
		int opt, a; scanf("%d%d", &opt, &a);
		if (opt == 1) {
            int b; scanf("%d", &b);
            w[a] ^= p[fa[a]], fa[a] = b;
            find(a);
        }
		else find(a), printf("%d\n", w[a] ^ p[fa[a]]);
	}
	return 0;
}
B
首先一看这个良心的数据范围,直接 \(\operatorname{DFS}\)
不过直接爆搜最后两个点会T,所以要加一个最优化剪枝,如果当前答案大于 \(ans\),那么直接退出
点击查看代码
#include <cstdio>
#include <queue>
const int N = 200005, inf = 2147483647;
struct edge{
    int to, nxt, dist;
}e[N];
int n, m, Q, head[N], tot;
int x, y, z, cnt, anss;
bool vis[N];
void add(int u, int v, int w) {
    e[++tot].to = v; e[tot].dist = w; e[tot].nxt = head[u]; head[u] = tot;
}
void DFS(int s, int ans, int num, int cnt) {
    if (s == y) {anss = std::min(anss, ans); return;}
    if (cnt + 1 > z || ans > anss) return;
    for (int i = head[s]; i; i = e[i].nxt) {
        int v = e[i].to;
        if (num < e[i].dist) 
            DFS(v, ans + e[i].dist, e[i].dist, cnt + 1);
    }
}
int main() 
{
    scanf("%d%d%d", &n, &m, &Q);
    for (int i = 1; i <= m; i++) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
    }
    while (Q--) {
        scanf("%d%d%d", &x, &y, &z);
        anss = inf;
        DFS(x, 0, -inf, 0);
        if (anss == inf) {puts("-1"); continue;}
        printf("%d\n", anss);
    }
    return 0;
}
正解
记 \(dis_{i,j,k}\) 表示 \(i->j\) 的长度不超过 \(k\) 的所有递增路径中,权值和最小的一条,将所有边排好序,按边转移即可
复杂度 \(O(n^2m)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=155,oo=1e9;
int n,m,q,f[N][N][N];
struct edge{
	int u,v,l;
}t[5005];
inline bool cmp(const edge &a,const edge &b){
	return a.l<b.l;
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&t[i].u,&t[i].v,&t[i].l);
	}
	sort(t+1,t+m+1,cmp);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			for(int k=0;k<=n;k++){
				f[i][j][k]=oo;
			}
		}
	}
	for(int i=1;i<=n;i++){
		f[i][i][0]=0;
		for(int j=1;j<=m;j++){
			int u=t[j].u,v=t[j].v,l=t[j].l;
			for(int k=0;k<n;k++){
				f[i][v][k+1]=min(f[i][v][k+1],f[i][u][k]+l);
			}
		}
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				f[i][j][k]=min(f[i][j][k],f[i][j][k-1]);
			}
		}
	}
	while(q--){
		int u,v,l;scanf("%d%d%d",&u,&v,&l);l=min(l,n);
		if(f[u][v][l]>=oo){
			puts("-1");
		}else{
			printf("%d\n",f[u][v][l]);
		}
	}
	return 0;
}
C
首先对于所有颜色集合有序维护所有出现位置,考虑对颜色集合大小分类,集合大小 \(>=\sqrt{n}\) 称为大类,数量不超过 \(\sqrt{n}\)
对于所有大类预处理到其他所有颜色集合的答案,扫一遍原序列即可,复杂度\(O(n\sqrt{n})\)
考虑查询操作,若有一个为大类,则\(O(1)\)查询,若两个都为小类,则两个有序数列合并一次可得到答案
考虑修改操作 \(x,y\),若 \(x,y\) 都为小集合,则直接暴力合并即可,若两个都为大集合,则暴力更新完重新预处理即可,大集合合并不会超过 \(\sqrt{n}\) 次
比较麻烦的就是一个大集合并上一个小集合,考虑对每个大类开一个小集合,并上的集合先塞入小集合中,表示后续需要并上的集合,查询大类的时候不止用预处理的信息,也查询大类所附带的小集合的信息,当小集合大小超过 \(\sqrt{n}\) 时重新预处理所有信息,不会超过 \(\sqrt{n}\) 次
总复杂度\(O((n+m)\sqrt{n})\)
点击查看代码
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define pb push_back
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define gt if(++ip==iend)fread(ip=ibuf,1,1<<21,stdin);
char ibuf[1<<21],*ip=ibuf+(1<<21)-1;
const char *iend=ibuf+(1<<21);
inline int in()
{
	static int k;k=0;gt;while(*ip<'-')gt;
	while(*ip>'-'){k=k*10+(*ip&15);gt;}
	return k;
}
using std::vector;using std::min;
const int N=1.5e5+5,SN=150;
struct node
{
	int sz,id,ans[SN];vector<int>cx,fs;
	node(){memset(ans,0x3f,sizeof ans);}
	inline void push(int pos){fs.pb(pos);++sz;}
}tmp[N],*p[N];
int a[N],cnt,n,m,sq,f[N],tt;
inline int Get(vector<int> &a,vector<int> &b)
{
	int sza=a.size(),szb=b.size(),ans=1e9,i=0,j=0;
	if(!sza||!szb)return ans;
	while(i<sza&&j<szb)
		if(a[i]>b[j])ans=min(ans,a[i]-b[j++]);
		else ans=min(ans,b[j]-a[i++]);
	return ans;
}
inline void merge(vector<int>&A,vector<int>&B)
{
	int n=A.size(),m=B.size();
	static int a[N],b[N];
	for(int i=0;i<n;++i)a[i]=A[i];
	for(int i=0;i<m;++i)b[i]=B[i];
	A.clear();B.resize(n+m);
	for(register int i=0,j=0,k=0;i<n+m;++i)
		if(j==n)B[i]=b[k++];
		else if(k==m)B[i]=a[j++];
		else if(a[j]<b[k])B[i]=a[j++];
		else B[i]=b[k++];
}
inline void rebuild(int now)
{
	static int mp[N],cnt,lst,id;
	merge(p[now]->fs,p[now]->cx);cnt=0,lst=-1e9,id=p[now]->id;
	for(int i=1;i<=N-5;++i)
		if(p[i]->sz){for(int x:p[i]->cx)mp[x]=i;for(int x:p[i]->fs)mp[x]=i;}
	for(int i=1;i<=n;++i)if(mp[i]==now)lst=i;
		else p[mp[i]]->ans[id]=min(p[mp[i]]->ans[id],i-lst);lst=1e9;
	for(int i=n;i>=1;--i)if(mp[i]==now)lst=i;
		else p[mp[i]]->ans[id]=min(p[mp[i]]->ans[id],lst-i);
}
inline void upd(int &x,int &y)
{
	if(!p[x]->sz||x==y)return;
	if(p[x]->sz>p[y]->sz)std::swap(x,y);
	for(int i=1;i<=cnt;++i)
		if(p[y]->id!=i&&p[x]->id!=i)
			p[y]->ans[i]=min(p[x]->ans[i],p[y]->ans[i]);
	memset(p[x]->ans,0x3f,cnt+2<<2);
	if(p[x]->sz>sq)
	{
		merge(p[x]->cx,p[y]->fs);merge(p[x]->fs,p[y]->fs);
		p[y]->sz+=p[x]->sz;p[x]->sz=0;rebuild(y);return;
	}
	if(p[y]->sz<=sq)
	{
		merge(p[x]->fs,p[y]->fs);p[y]->sz+=p[x]->sz;p[x]->sz=0;
		if(p[y]->sz>sq)p[y]->id=++cnt,rebuild(y);return;
	}
	merge(p[x]->fs,p[y]->fs);p[y]->sz+=p[x]->sz;p[x]->sz=0;
	if(p[y]->fs.size()>sq)rebuild(y);return;
}
inline int query(int x,int y)
{
	if(x==y)return 0;if(p[x]->sz>p[y]->sz)std::swap(x,y);
	if(p[x]->sz>sq)return min(min(p[x]->ans[p[y]->id],p[y]->ans[p[x]->id]),Get(p[x]->fs,p[y]->fs));
	if(p[y]->sz<=sq)return Get(p[x]->fs,p[y]->fs);
	return min(p[x]->ans[p[y]->id],Get(p[x]->fs,p[y]->fs));
}
int main()
{
	n=in(),m=in(),sq=sqrt(n)*4;
	for(int i=0;i<=N-5;++i)p[i]=&tmp[i],f[i]=i;
	for(int i=1;i<=n;++i)a[i]=in(),p[a[i]]->push(i);
	for(int i=1;i<=N-5;++i)if(p[i]->sz>sq)p[i]->id=++cnt,rebuild(i);
	int lst=0,opt,x,y,ans;
	while(m--)
	{
		opt=in(),x=in()^lst,y=in()^lst;
		if(opt==1)upd(f[x],f[y]);
		if(opt==2)
			if(!p[f[x]]->sz||!p[f[y]]->sz)lst=0,puts("0");
			else printf("%d\n",lst=query(f[x],f[y]));
	}
	return 0;
}
D
可以发现操作1就是查询区间的乘积对 \(p1\) 取模,操作2就是查询区间的乘积对 \(phi(p2)\) 取模,用线段树直接维护就好(甚至连修改操作都没有)
不过这样最后四个点明显会T
是时候发挥我那娴熟的卡常技巧了(好吧,这题卡常真能过
点击查看代码
#include <cstdio>
#include <cctype>
#include <cmath>
#define ll long long
const int BUF_SIZE = 30;
char buf[BUF_SIZE], *buf_s = buf, *buf_t = buf + 1;
#define PTR_NEXT() \
    { \
        buf_s ++; \
        if (buf_s == buf_t) \
        { \
            buf_s = buf; \
            buf_t = buf + fread(buf, 1, BUF_SIZE, stdin); \
        } \
    }
#define readint(_n_) \
    { \
        while (*buf_s != '-' && !isdigit(*buf_s)) \
            PTR_NEXT(); \
        bool register _nega_ = false; \
        if (*buf_s == '-') \
        { \
            _nega_ = true; \
            PTR_NEXT(); \
        } \
        int register _x_ = 0; \
        while (isdigit(*buf_s)) \
        { \
            _x_ = _x_ * 10 + *buf_s - '0'; \
            PTR_NEXT(); \
        } \
        if (_nega_) \
            _x_ = -_x_; \
        (_n_) = (_x_); \
    }
#define readstr(_s_) \
    { \
        while (!islower(*buf_s)) \
            PTR_NEXT(); \
        char register *_ptr_ = (_s_); \
        while (islower(*buf_s)) \
        { \
            *(_ptr_ ++) = *buf_s; \
            PTR_NEXT(); \
        } \
        (*_ptr_) = '\0'; \
    }
#define ls(x) x << 1
#define rs(x) x << 1 | 1
const int N = 1e7 + 5;
ll p1, p2, n, m, k, in[N], qp[(1 << 16) + 5][2], B;
ll phi, ans[N << 2], ans2[N << 2];
inline void write(ll x) {
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
inline ll qphi(ll n) {
    int ans = n;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            ans = ans / i * (i - 1);
        	while (n % i == 0) n /= i;
    	}
	}
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}
inline void pushup(int i) {
	ans[i] = (ans[ls(i)] * ans[rs(i)]) % p1;
	ans2[i] = (ans2[ls(i)] * ans2[rs(i)]) % phi;
}
inline void build(ll p, ll l, ll r) {
	if (l == r) {ans[p] = ans2[p] = in[l]; return;} 
	ll mid = (l + r) >> 1;
	build(ls(p), l, mid); build(rs(p), mid + 1, r);
	pushup(p);
}
inline ll cx(ll nl, ll nr, ll l, ll r, ll p) {
	ll res = 1;
	if (nl <= l && nr >= r) return ans[p];
	ll mid = (l + r) >> 1;
	if (nl <= mid) res = res * cx(nl, nr, l, mid, ls(p)) % p1;
	if (nr > mid) res = res * cx(nl, nr, mid + 1, r, rs(p)) % p1;
	return res;
}
inline ll cx2(ll nl, ll nr, ll l, ll r, ll p) {
	ll res = 1;
	if (nl <= l && nr >= r) return ans2[p];
	ll mid = (l + r) >> 1;
	if (nl <= mid) res = res * cx2(nl, nr, l, mid, ls(p)) % phi;
	if (nr > mid) res = res * cx2(nl, nr, mid + 1, r, rs(p)) % phi;
	return res;
}
inline void init() {
    qp[0][0] = qp[0][1] = 1;
    for (int i = 1; i <= B; ++i)
        qp[i][0] = qp[i - 1][0] * k % p2;
    for (int i = 1; i <= B; ++i)
        qp[i][1] = qp[i - 1][1] * qp[B][0] % p2;
}
inline ll mul(ll y) {
	return qp[y % B][0] * qp[y / B][1] % p2;
}
int main()
{
	readint(n); readint(m); readint(k); readint(p1); readint(p2);
	if (!p1) p1++;
	if (!p2) p2++;
    B = std::sqrt(p2), phi = qphi(p2); init();
    for (int i = 1; i <= n; i++) readint(in[i]);
	build(1, 1, n);
	for (int i = 1; i <= m; i++) {
        int reo, x, y; readint(reo); readint(x); readint(y);
		if (reo == 1) write(cx(x, y, 1, n, 1)), putchar(10);
		else write(mul(cx2(x, y, 1, n, 1))), putchar(10);
	}
	return 0;
}
考虑如果不卡常怎么办
第七个点只有1操作,而且模数是质数,预处理前缀积,查询区间 \([l,r]\) 的乘积等于 \(r\) 的前缀积乘以 \(l-1\) 的前缀积的逆
第八个点也只有1操作,而且模数可以分解成三个数,三个分别求最后 \(\operatorname{crt}\) 合并即可,最后两个点同理
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N=500010;
int n,m,k,p1,p2,phi;
int v[20][N],f[20][N],fa[20][N],a[N*2];
inline int gi() {
    int x=0,o=1;
    char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') ch=getchar(),o=-1;
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*o;
}
inline int calc(int x) {
    int t=x,ret=x;
    for(int i=2;i*i<=x;i++)
	if(t%i==0) {
	    while(t%i==0) t/=i;
	    (ret/=i)*=i-1;
	}
    if(t>1) (ret/=t)*=t-1;
    return ret;
}
inline int quev(register int l,register int r) {
    register int ret=a[l]%p1;
    for(register int i=19;i>=0;i--)
	if(fa[i][l]&&fa[i][l]<=r)
	    ret=1ll*ret*v[i][l]%p1,l=fa[i][l];
    return ret;
}
inline int quef(register int l,register int r) {
    register int ret=a[l]%phi;
    for(register int i=19;i>=0;i--)
	if(fa[i][l]&&fa[i][l]<=r)
	    ret=1ll*ret*f[i][l]%phi,l=fa[i][l];
    return ret;
}
inline int Pow(int x,int y) {
    int ret=1;
    while(y) {
	if(y&1) ret=1ll*ret*x%p2;
	x=1ll*x*x%p2,y>>=1;
    }
    return ret;
}
inline void Exgcd(int a,int b,int &x,int &y) {
    if(!b) {x=1,y=0;return;}
    Exgcd(b,a%b,y,x),y-=a/b*x;
}
inline void work1() {
    for(int i=2;i<=n;i++) a[i]=1ll*a[i-1]*a[i]%p1;
    while(m--) {
	gi();
	int l=gi(),r=gi(),x,y;
	if(l==1) printf("%d\n",a[r]);
	else {
	    Exgcd(a[l-1],p1,x,y);
	    x%=p1;
	    while(x<0) x+=p1;
	    printf("%lld\n",1ll*x*a[r]%p1);
	}
    }
}
inline void work2() {
    for(int i=2;i<=n;i++) a[i]=1ll*a[i-1]*a[i]%phi;
    while(m--) {
	gi();
	int l=gi(),r=gi(),x,y;
	if(l==1) printf("%d\n",Pow(k,a[r]));
	else {
	    Exgcd(a[l-1],phi,x,y);
	    x%=phi;
	    while(x<0) x+=phi;
	    printf("%d\n",Pow(k,1ll*x*a[r]%phi));
	}
    }
}
int main() {
    cin>>n>>m>>k>>p1>>p2;
    phi=calc(p2);
    for(int i=1;i<=n;i++) a[i]=gi();
    if(n>500000) {
	if(p1) work1();
	else work2();
	return 0;
    }
    for(int i=1;i<n;i++) {
	fa[0][i]=i+1;
	if(p1) v[0][i]=a[i+1]%p1;
	if(p2) f[0][i]=a[i+1]%phi;
    }
    for(int i=1;i<20;i++)
	for(int j=1;j<n;j++) {
	    fa[i][j]=fa[i-1][fa[i-1][j]];
	    if(p1) v[i][j]=1ll*v[i-1][j]*v[i-1][fa[i-1][j]]%p1;
	    if(p2) f[i][j]=1ll*f[i-1][j]*f[i-1][fa[i-1][j]]%phi;
	}
    while(m--) {
	int op=gi(),l=gi(),r=gi();
	if(op==1) printf("%d\n",quev(l,r));
	else printf("%d\n",Pow(k,quef(l,r)));
    }
    return 0;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号