2023.10.11~2023.10.17测试

\[\text{NOIP模拟赛 2023.10.11~2023.10.17} \]

2023.10.11

T1 染色

给定 \(n\),需要给整数 \(1\sim n\) 染色,使得对于所有 \(1\leq i\leq j\leq n\),若 \(j-i\) 为质数,则 \(i,j\) 不同色。求颜色最少的染色方案,输出任意一种方案

\(1\leq n\leq 10000\)

诈骗题

观察到若 \(j=i+4\)\(i,j\) 可同色,所以答案上界为 \(4\),而样例中 \(n=7\) 的答案已经是 \(4\),所以 \(n\ge 7\) 的全都是 \(4\),小于 \(7\) 的打表即可

code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;

const int N=1e4+10;

int n;

int main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	
//	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
	
	scanf("%d",&n);
	if(n==1)
		printf("1\n1");
	else if(n==2)
		printf("1\n1 1");
	else if(n==3)
		printf("2\n1 1 2");
	else if(n==4)
		printf("2\n1 1 2 2");
	else if(n==5)
		printf("3\n1 1 2 2 3");
	else if(n==6)
		printf("3\n1 1 2 2 3 3");
	else
	{
		puts("4");
		for(int i=1; i<=n; i++)
			printf("%d ",i%4+1);
	} 
	
	return 0;
}

T2 序列

给定一个长度为 \(m\) 的序列 \(a\),有一个长度为 \(m\) 的序列 \(b\),需满足 \(0\leq b_i\leq n\)\(\sum\limits_{i=1}^m {a_ib_i}\leq D\)\(d_i\) 为整数

\(\sum\limits_{i=1}^mb_i+k\min\{b_i\}\) 的最大值

\(1\leq T\leq 5\)\(1\leq n\leq 10^9\)\(1\leq k,m\leq 2\times 10^5\)\(1\leq D\leq 10^{18}\)\(1\leq a_i\leq 5000\)

这个 \(\min\) 很难受啊,考虑钦定最小值 \(mn\)

从贪心来说显然是先考虑小的 \(a_i\),将 \(a\) 从小到大排序,令 \(b_m=mn\) 并让 \(b_1\sim b_{m-1}\) 也先填上 \(mn\)。接下来要想最大化 \(\sum b_i\),显然是填的 \(n\) 越多越好(当然也可以证明这样是最优的),所以考虑二分出最后一个填 \(n\) 的位置 \(pos\),那么再令 \(b_{pos+1}\) 填一个最大能填的数即可

所以,在钦定 \(mn\) 后,答案在 \(\mathcal{O}(\log m)\) 的时间内求出。而根据打表可以发现答案是一个关于 \(\min\{b_i\}\) 的非严格单峰函数,所以可以使用三分法求出答案(当然要点特殊方法,但是数据水直接正常三分过了)

综上,突破口在于钦定 \(\min\{b_i\}\),最难的地方在于看出这是一个单峰函数,在考场上应充分使用打表等工具观察性质

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10;

int T,m;
LL n,k,D,a[N],s[N];

int find2(LL mn,LL d)
{
	if(m<=2)
		return 0;
	int lo=0,hi=m-2;
	while(lo+1<hi)
	{
		int mid=(lo+hi)/2;
		if((n-mn)*s[mid]<=d)
			lo=mid;
		else
			hi=mid;
	}
	return lo;
}

LL calc(LL mn)
{
	if(mn<0)
		return 0;
	LL res=mn*m+mn*k,d=D-mn*s[m],add=0;
	if(d<0)
		return 0;
	LL pos=find2(mn,d);
	d-=(n-mn)*s[pos];  
	res+=(n-mn)*pos+min(n-mn,d/a[pos+1]);
	return res;
}

LL find(LL l,LL r)
{
	LL lo=l,hi=r;
	while(lo+2<hi)
	{
		LL lmid=lo+(hi-lo)/3;
		LL rmid=hi-(hi-lo)/3;
		if(calc(lmid)>=calc(rmid))
			hi=rmid;
		else
			lo=lmid;
	}
	return (lo+hi)/2;
}

void mian()
{
	scanf("%lld%d%lld%lld",&n,&m,&k,&D);
	for(int i=1; i<=m; i++)
		scanf("%lld",&a[i]);
		
	sort(a+1,a+1+m);
	
	for(int i=1; i<=m; i++)
		s[i]=s[i-1]+a[i];
	
	LL mn=find(0,min(n,D/s[m])); 
	LL ans=calc(mn);
	for(int i=1; i<=10; i++)
		ans=max(ans,max(calc(mn+i),calc(mn-i)));
	
	printf("%lld\n",ans);
}

int main()
{
	freopen("array.in","r",stdin);
	freopen("array.out","w",stdout);
	
	scanf("%d",&T);
	while(T--)
		mian();

	return 0;
}

T3 树上询问

一棵 \(n\) 个节点的树,每次询问给定 \(l,r\),询问存在多少个整数 \(k\),使得从 \(l\) 沿着 \(l\rightarrow r\) 的简单路径走 \(k\) 步恰好走到 \(k\)

\(1\leq n,m\leq 3\times 10^5\)

简单题,但是树上倍增预处理写错了 \(100\rightarrow 5\)

将一条 \(l\rightarrow r\) 的简单路径转化成 \(l\rightarrow lca\rightarrow r\),对于 \(l\rightarrow lca\) 这条路径上的点,需满足 \(dep_l-dep_x=x\)\(dep_x+x=dep_l\)。对于 \(lca\rightarrow r\) 这条路径上的点,需满足 \(dep_l-dep_{lca}+dep_x-dep_{lca}=x\)\(dep_x-x=2\times dep_{lca}-dep_l\)。树上差分+主席树维护即可

code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;

bool Mbe;

const int N=3e5+10;

int n,m,a[2][N],b[2][N],rev[2][N],m0,m1;
vector <int> g[N];

namespace Seg
{
	int rt[N][2];
	struct SegmentTree
	{
		int lc[N*40],rc[N*40],sum[N*40],tot;
		#define lc(x) lc[x]
		#define rc(x) rc[x]
		#define sum(x) sum[x]
		
		void pushup(int p)
		{
			sum(p)=sum(lc(p))+sum(rc(p));
		}
		
		void build(int &p,int l,int r)
		{
			p=++tot;
			if(l==r)
				return sum(p)=0,void();
			int mid=(l+r)>>1;
			build(lc(p),l,mid);
			build(rc(p),mid+1,r);
		}
		
		void change(int &p,int pre,int l,int r,int x,int v)
		{
//			cout<<l<<" "<<r<<endl;
			p=++tot;
			lc(p)=lc(pre);  rc(p)=rc(pre);  sum(p)=sum(pre);
			if(l==r)
				return sum(p)+=v,void();
			int mid=(l+r)>>1;
			if(x<=mid)
				change(lc(p),lc(pre),l,mid,x,v);
			else
				change(rc(p),rc(pre),mid+1,r,x,v);
			pushup(p);
		}
		
		int ask(int p,int pre,int l,int r,int x)
		{
			if(!p)
				return 0;
			if(l==r)
				return sum(p)-sum(pre);
			int mid=(l+r)>>1;
			if(x<=mid)
				return ask(lc(p),lc(pre),l,mid,x);
			return ask(rc(p),rc(pre),mid+1,r,x); 
		}
	}t0,t1;
}
using namespace Seg;

namespace TR
{
	int dep[N],f[N][25];
	
	void dfs1(int x,int fa)
	{
		dep[x]=dep[fa]+1;
		for(int y:g[x])
		{
			if(y==fa)
				continue;
			f[y][0]=x;
			for(int i=1; i<=20; i++)
				f[y][i]=f[f[y][i-1]][i-1];
			dfs1(y,x);
		}
	}
	
	void dfs2(int x,int fa)
	{
		t0.change(rt[x][0],rt[fa][0],1,m0,a[0][x],1);
		t1.change(rt[x][1],rt[fa][1],1,m1,a[1][x],1);
		for(int y:g[x])
			if(y!=fa)
				dfs2(y,x);
	}
	
	int LCA(int x,int y)
	{
		if(dep[x]<dep[y])
			swap(x,y);
		for(int i=20; i>=0; i--)
			if(dep[f[x][i]]>=dep[y])
				x=f[x][i];
        if(x==y)
			return x;
		for(int i=20; i>=0; i--)
			if(f[x][i]!=f[y][i])
				x=f[x][i],y=f[y][i];
		return f[x][0];	
	}
}
using namespace TR;

bool Med;

int main()
{
//	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
	
	freopen("query.in","r",stdin);
	freopen("query.out","w",stdout);

	scanf("%d%d",&n,&m);
	for(int i=1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	
	dfs1(1,0);
	
	for(int i=1; i<=n; i++)
	{
		a[0][i]=b[0][i]=dep[i]+i;
		a[1][i]=b[1][i]=dep[i]-i;
	}
	sort(b[0]+1,b[0]+1+n);
	sort(b[1]+1,b[1]+1+n);
	m0=unique(b[0]+1,b[0]+1+n)-(b[0]+1);
	m1=unique(b[1]+1,b[1]+1+n)-(b[1]+1);
	for(int i=1; i<=n; i++)
	{
		int x=lower_bound(b[0]+1,b[0]+1+m0,a[0][i])-b[0];
		rev[0][x]=a[0][i];  a[0][i]=x;
		int y=lower_bound(b[1]+1,b[1]+1+m1,a[1][i])-b[1];
		rev[1][y]=a[1][i];  a[1][i]=y;
	}
	
	t0.build(rt[0][0],1,m0);
	t1.build(rt[0][1],1,m1);
	dfs2(1,0);
	
	while(m--)
	{
		int l,r,lca,ans=0;
		scanf("%d%d",&l,&r);
		
		lca=LCA(l,r);
		int x=lower_bound(b[0]+1,b[0]+1+m0,dep[l])-b[0];
		if(b[0][x]==dep[l])
			ans+=t0.ask(rt[l][0],rt[lca][0],1,m0,x);
		int y=lower_bound(b[1]+1,b[1]+1+m1,2*dep[lca]-dep[l])-b[1];
		if(b[1][y]==2*dep[lca]-dep[l])
			ans+=t1.ask(rt[r][1],rt[lca][1],1,m1,y);
		if(dep[l]-dep[lca]==lca)
			ans++;
        
		printf("%d\n",ans);
	}

	return 0;
}

T4 莫队

(与莫队无关)

给出一个长度为 \(n\) 的序列 \(a\),支持单点修改,或者查询区间 \([l,r]\) 内有多少个无重的子区间

\(n,m\leq 2\times 10^5\)

\(f_i\) 表示 \(i\) 往右第一次出现 \(a_i\) 的位置,那么一个区间 \([l,r]\) 的,当且仅当 \(r<\min\limits_{i=l}^r\{f_i\}\)

一个区间 \([l,r]\) 的合法子区间个数为

\[\sum_{i=l}^r{\min\{\min\limits_{j=i}^r\{f_j\},r+1\}-i} \]

后缀 \(\min\) 优秀的性质使得我们可以用线段树维护区间后缀 \(\min\) 的和

类似于 P4198 楼房重建,合并两个区间时,\(dat(rc(p))\) 可以直接累加,但是对于左区间来说,可能会存在一段是大于 \(mn(rc(p))\) 的,那么这一段的贡献以 \(mn(rc(p))\) 累计,剩下的一段就是原来的 \(dat\)

具体地,设 \(merge(val,p,l,r)\) 表示当右边最小值为 \(val\) 时区间 \([l,r]\) 的贡献。那么如果此时的右区间最小值已经小于 \(val\),那断点肯定在右区间里,递归右区间即可

否则,右区间贡献以 \(val\) 累计,递归左区间即可

还要用个 set 维护 \(f_i\)

code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10;

int n,m,a[N],f[N],hi;
LL res;
set <int> s[N];

#define lc(p) p<<1
#define rc(p) p<<1|1
struct SegmentTree
{
	int mn; LL dat;
	#define mn(x) tree[x].mn
	#define dat(x) tree[x].dat
}tree[N<<2];

LL merge(int val,int p,int l,int r)
{
	if(l==r)
		return min(dat(p),(LL)val);
	int mid=(l+r)>>1;
	if(mn(rc(p))<val)
		return dat(p)-dat(rc(p))+merge(val,rc(p),mid+1,r);
	return merge(val,lc(p),l,mid)+1LL*val*(r-mid);
}

void pushup(int p,int l,int r)
{
	mn(p)=min(mn(lc(p)),mn(rc(p)));
	dat(p)=dat(rc(p))+merge(mn(rc(p)),lc(p),l,(l+r)>>1);
}

void build(int p,int l,int r)
{
	if(l==r)
	{
		dat(p)=mn(p)=f[l];
		return;
	}
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	pushup(p,l,r);
}

void change(int p,int l,int r,int x,int v)
{
	if(l==r)
	{
		dat(p)=mn(p)=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
		change(lc(p),l,mid,x,v);
	else
		change(rc(p),mid+1,r,x,v);
	pushup(p,l,r); 
}

void ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l && qr>=r)
	{
		res+=merge(hi,p,l,r);
		hi=min(hi,mn(p));
		return;
	}
	int mid=(l+r)>>1;
	if(qr>mid)
		ask(rc(p),mid+1,r,ql,qr);
	if(ql<=mid)
		ask(lc(p),l,mid,ql,qr);
}

int main()
{
	freopen("team.in","r",stdin);
	freopen("team.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]),s[i].insert(n+1);
	
	for(int i=n; i>=1; i--)
		f[i]=*(s[a[i]].begin()),s[a[i]].insert(i);
	
	build(1,1,n);
	
	while(m--)
	{
		int op,l,r,x,y;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d",&x,&y);
			auto it=s[a[x]].find(x);
			int nxt=*(++it);  it--;
			if(it!=s[a[x]].begin())
				it--,change(1,1,n,*it,nxt);
			s[a[x]].erase(x);  a[x]=y;
			it=s[y].lower_bound(x);
			change(1,1,n,x,*it);
			if(it!=s[y].begin())
				it--,change(1,1,n,*it,x);
			s[y].insert(x);
		}
		else
		{
			scanf("%d%d",&l,&r);
			res=0;  hi=r+1;
			ask(1,1,n,l,r);
			res-=1LL*(r-l+1)*(l+r)/2;
			printf("%lld\n",res); 
		}
	}

	return 0;
}

\(100+0+5+0=105\),纯唐

2023.10.12

联考?666

T1 异或

有一个 \(n\times n\) 的矩阵 \(A\),初始所有元素均为 \(0\)

执行 \(q\) 次如下的操作,每次操作形如 \((r,c,l,s)\),即对于 \(x\in[r,r+l),y\in[c,c+x-r]\) 的元素 \((x,y)\) 加上 \(s\),求最后矩阵元素的异或和

\(n\leq 10^3\)\(q\leq 3\times 10^5\)

就是给一个等腰直角三角形全部加上 \(s\),写个差分没了

code
#include<bits/stdc++.h>
#define LL long long 
using namespace std;

const int N=2e3+10;

int n,q;
LL b[N][N],d[N][N],ans;

int main()
{
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	
	scanf("%d%d",&n,&q);
	
	while(q--)
	{
		int r,c,l,s,tx=0,ty=0;
		scanf("%d%d%d%d",&r,&c,&l,&s);
		
		tx=r+l-1;  ty=tx-r+c;
		d[r][c]+=(LL)s;  d[tx+1][c]-=(LL)s;
		b[tx+1][c+1]-=(LL)s;  b[tx+1][ty+2]+=(LL)s;
	}
	
	for(int i=0; i<=n+2; i++)
	{
		for(int j=1; j<=n+2; j++)
			d[j][i]+=d[j-1][i];
	}
	for(int i=0; i<=n+2; i++)
	{
		for(int j=1; j<=n+2; j++)
			b[i][j]+=b[i][j-1];
	} 
	
	for(int i=1; i<=n; i++)
	{
		for(int j=1; j<=n; j++)
		{
			d[i][j]+=d[i-1][j-1]+b[i][j];
			ans^=d[i][j]; 
		}
	}
	
	printf("%lld",ans);

	return 0;
}


T2 游戏

Alice 和 Bob 在博弈,共 \(m\) 轮,两人轮流操作

一开始有集合 \(A=\{a_1,a_2,\dots,a_n\}\),每轮操作有两种选择,保留 \(A\) 中所有 \(b_i\) 的倍数,或者保留 \(A\) 中所有非 \(b_i\) 的倍数。

Alice 想要让最后 \(A\) 中的数字最小,Bob 想要让其最大,求最后的答案

\(1\leq n\leq 2\times 10^4\)\(1\leq m\leq 2\times 10^5\),$ -10^{14}\leq a_i\leq 10^{14}$,\(1\leq b_i\leq 10^{14}\)

博弈一点不会好吧。写个部分分还因为判断条件写错挂成 \(0\)

\(\mathcal{O}(n2^m)\) 比较好想,复杂度高在于有很多无用状态。因为每个元素在一次操作后只会分向一边,所以考虑拿一个二叉树维护这个操作。建立一个 \(m\) 层的二叉树,如果 \(a_i\)\(b_{dep}\) 的倍数就往左边插入,否则往右边插入。时间复杂度 \(\mathcal{O}(nm)\)

最后我们需要一点观察,我们发现每次操作中如果先手选择大的那一侧,那么至多 \(\log n\) 次后答案就会变成 \(0\),后手同理。又因为先后手轮流操作,所以当 \(m>2\log n\) 时,答案必为 \(0\),这样复杂度就是 \(\mathcal{O}(n\log n)\)

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e4+10,M=2e5+10;

int n,m,tot,rt;
LL a[N],b[M];

struct Tree
{
	int lc,rc;  LL sum;
	#define lc(x) tree[x].lc
	#define rc(x) tree[x].rc
	#define sum(x) tree[x].sum
}tree[N<<2];

void insert(int &p,int dep,LL x)
{
	if(!p)
		p=++tot;
	if(dep==m+1)
		return sum(p)+=x,void();
	if(x%b[dep]==0)
		insert(lc(p),dep+1,x);
	else
		insert(rc(p),dep+1,x);
}

LL query(int p,int dep)
{
	if(!p)
		return 0;
	if(dep==m+1)
		return sum(p);
	LL lval=query(lc(p),dep+1);
	LL rval=query(rc(p),dep+1);
	if(dep&1)
		return min(lval,rval);
	return max(lval,rval);
}

int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%lld",&a[i]);
	for(int i=1; i<=m; i++)
		scanf("%lld",&b[i]);
	
	if(m>2*(log2(n)+1))
	{
		printf("0");
		return 0;
	}
	
	for(int i=1; i<=n; i++)
		insert(rt,1,a[i]);
	
	printf("%lld",query(rt,1));

	return 0;
}

T3 联通块

一个 \(n\) 个点的图,点权 $\rm gcd $ 为合数的点之间连边。求一个点后剩下的最大联通块的最小值

\(T\leq 10\)\(n\leq 10^5\)\(a_i\leq 10^5\)

考场上有差不多的想法,但是不完善,暴搜数组开小只拿了 \(8\)

暴力来说建边和计算都是 \(\mathcal{O}(n^2)\)

考虑快速建边,定义单位合数为可以表示成两个质数乘积的合数,对每个单位合数建一个虚点,令每个点与自己的单位合数因数连边,可以快速建边。

对于计算,首先观察可发现肯定是割掉最大联通块里的一个割点,最后再计算最大联通块。割点用 Tarjan 求即可

但是 Tarjan 求割点到底写不写栈?我也不清楚,不写栈是错的,那就写栈吧

code
#pragma optimize GCC(3,"Ofast","inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=4e6+10,M=1e7+10,R=1e7+10;

int T,n,a[N],mxa,ans;
int prime[R],v[R],tp,id[R],tt;
int dfn[N],low[N],cut[N],siz[N],cnt,sta[N],top;
int head[N],ver[M],nxt[M],tot=1,node;
int fa[N],wei[N];

void prework()
{
	for(int i=2; i<=1e7; i++)
	{
		if(!v[i])
		{
			prime[++tp]=i;
			v[i]=i;
		}
		for(int j=1; j<=tp; j++)
		{
			if(prime[j]>v[i] || prime[j]>1e7/i)
				break;
			v[i*prime[j]]=prime[j];
		}
	}
	
	for(int i=2; i<=1e7; i++)
		if(v[i]!=i && v[i/v[i]]==i/v[i])
			id[i]=++tt;
}

void init()
{
	tot=1;  mxa=cnt=top=0;
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(cut,0,sizeof(cut));
	memset(siz,0,sizeof(siz));
}

int get(int x)
{
	if(x==fa[x])
		return x;
	return fa[x]=get(fa[x]);
}

void merge(int x,int y)
{
	int fx=get(x),fy=get(y);
	if(fx==fy)
		return;
	fa[fx]=fy;  wei[fy]+=wei[fx]; 
}

void add(int x,int y)
{
	ver[++tot]=y;  nxt[tot]=head[x];  head[x]=tot;
	ver[++tot]=x;  nxt[tot]=head[y];  head[y]=tot;
}

void add_G()
{
	node=tt+n; 
	for(int i=1; i<=n; i++)
	{
		int p[15]={0},c[15]={0};
		int x=a[i];
		while(x>1)
		{
			int w=v[x];
			p[++p[0]]=w;
			while(x%w==0)
				c[p[0]]++,x/=w;
		}
		
		for(int j=1; j<=p[0]; j++)
		{
			for(int k=j+(c[j]<=1); k<=p[0]; k++)
			{
				if(1LL*p[j]*p[k]<1e7)
				{
					int v=id[p[j]*p[k]]+n;
					add(i,v);
					merge(i,v);
				}
			}
		}
	}
}

void tarjan(int x,int root,int large)
{
	dfn[x]=low[x]=++cnt; 
	sta[++top]=x;
	if(x<=n)
		siz[x]=1;
	int flag=0,sum=0,res=1;
	for(int i=head[x]; i; i=nxt[i])
	{
		int y=ver[i];
		if(!dfn[y])
		{
			tarjan(y,root,large);
			low[x]=min(low[x],low[y]);
			if(dfn[x]<=low[y])
			{
				flag++;
				if(flag>1 || x!=root)
					cut[x]=1;
			}
			if(dfn[x]==low[y])
			{
				int z,s=0;
				do
				{
					z=sta[top--];
					siz[x]+=siz[z];
					s+=siz[z];
				}while(z!=y);
				res=max(res,s);
			}
		}
		else
			low[x]=min(low[x],dfn[y]);
	}
	
	if(cut[x])
		res=max(res,large-siz[x]);
	else
		res=large-1;
	if(x<=n)
		ans=min(ans,res);
} 

void mian()
{
	init();
	
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
		
	for(int i=1; i<=4e6; i++)
		fa[i]=i,wei[i]=(i<=n);
	
	add_G();
	
	int mx=0,rt=0,se=0;
	for(int i=1; i<=node; i++)
	{
		if(fa[i]!=i || !wei[i])
			continue;
		if(wei[i]>mx)
			se=mx,mx=wei[i],rt=i;
		else if(wei[i]>se)
			se=wei[i];
	}
	ans=mx;
	tarjan(rt,rt,mx);

	printf("%d\n",max(ans,se));
}

int main()
{
	freopen("connect.in","r",stdin);
	freopen("connect.out","w",stdout);
	
	prework();
	
	scanf("%d",&T);
	while(T--)
		mian();

	return 0;
}

2023.10.13

(牛客场)

T1 矩阵交换

一个 \(n\times m\) 的矩阵 \(A\)\(A_{i,j}\in\{1,2,3\}\)。每次可以任意交换两行,问能否使每列单调不降

\(T,n\leq 100\)

签到题,写了 \(1.5\rm h\),纯唐

code
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;

const int N=110;

int T,n,m,a[N][N],cnt;
struct node{int id,val;}ans[N];
vector <pii> pos;
pii tmp[N];

void init()
{
	memset(a,0,sizeof(a));
	int len=pos.size();
	for(int i=0; i<len; i++)
		pos.pop_back();
}

void mian()
{
	init();
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
	{
		ans[i].id=i;
		for(int j=1; j<=m; j++)
			scanf("%d",&a[i][j]);
	}
	
	pos.push_back({1,n});
	for(int i=1; i<=m; i++)
	{
		for(int j=1; j<=n; j++)
			ans[j].val=a[ans[j].id][i];
			
		cnt=0;
		for(auto v:pos)
		{
			sort(ans+v.first,ans+v.second+1,[](node x,node y){return x.val<y.val;});
			int l=v.first,r=v.first;
			for(int j=v.first+1; j<=v.second; j++)
			{
				r++;
				if(a[ans[j].id][i]!=a[ans[j-1].id][i])
					tmp[++cnt]={l,r-1},l=r;	
			}
			tmp[++cnt]={l,r};
		}
		
		int len=pos.size();
		for(int j=0; j<len; j++)
			pos.pop_back();
		for(int j=1; j<=cnt; j++)
			pos.push_back(tmp[j]);
	} 
	
	for(int i=1; i<=m; i++)
	{
		for(int j=2; j<=n; j++)
		{
			if(a[ans[j].id][i]<a[ans[j-1].id][i])
			{
				printf("NO\n");
				return;
			}
		}
	}
	printf("YES\n");
}

int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	
	scanf("%d",&T);
	while(T--)
		mian(); 

	return 0;
}

T2 砖块摆放

原题??[ARC117C] Tricolor Pyramid

好题

这个操作很难直接做,所以考虑将操作转化成一些数字上的操作

一个牛逼的转化是令 \(A,B,C\) 分别等于 \(0,1,2\),每次操作即 \(2(x+y)\bmod 3\)

而我在考场上贺了 SError 的做法,令 \(A,B,C\) 分别等于 \(1,2,4\),每次操作即求 \((xy)^{-1}\pmod 7\)

然后通过枚举与打表可以发现,对于第 \(i\) 个砖块来说,对顶上的贡献即 \(a_i^{\binom{n-1}{i-1}}\) 如果 \(n\) 是偶数的话还要再求一次逆元

\(x=\dbinom{n-1}{i-1}\),因为 \(x\) 很大,所以欧拉降幂得 \(a_i^{x\bmod \varphi(7)}=a_i^{x\bmod 6}\)。所以即求 \(\dbinom{n-1}{i-1} \bmod 6\)。由于 \(6\) 不是质数,所以不能直接用 \(\rm Lucas\) 定理,但是我们可以先对 \(2\)\(3\)\(\rm Lucas\) 定理,再用中国剩余定理求出来

代码不难写

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10,MOD=7;

int T,n,a[N];
char s[N];
int c[5][5];

int ksm(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1)
			res=1LL*res*x%MOD;
		x=1LL*x*x%MOD;
		y>>=1;
	}
	return res;
}

void prework()
{
	c[0][0]=1;
	c[1][0]=c[1][1]=1;
	c[2][0]=1;  c[2][1]=2;  c[2][2]=1;
	c[3][0]=1;  c[3][1]=c[3][2]=3;  c[3][3]=1;
}

int C(int x,int y,int p)
{
	return c[x][y];
}

int lucas(int x,int y,int p)
{
	if(y==0)
		return 1;
	if(x<p && y<p)
		return C(x%p,y%p,p);
	return 1LL*C(x%p,y%p,p)*lucas(x/p,y/p,p)%p;
}

int crt(int a1,int a2)
{
	int m=6,m1=2,m2=3;
	int M1=3,M2=2;
	int t1=ksm(M1,m1-2),t2=ksm(M2,m2-2);
	int ans=1LL*(1LL*a1*M1%m*t1%m+1LL*a2*M2%m*t2%m)%m;
	return ans;
}

void mian()
{
	scanf("%d%s",&n,s+1);
	for(int i=1; i<=n; i++)
	{
		if(s[i]=='A')
			a[i]=1;
		else if(s[i]=='B')
			a[i]=2;
		else
			a[i]=4;
	}
	
	int ans=1; 
	for(int i=1; i<=n; i++)
	{
		int ind1=lucas(n-1,i-1,2),ind2=lucas(n-1,i-1,3),res=1;
		int ind=crt(ind1,ind2);
		res=ksm(a[i],ind);
		if(n%2==0)
			res=ksm(res,MOD-2);	
		
		ans=1LL*ans*res%MOD;
	}
	
	if(ans==1)
		puts("A");
	else if(ans==2)
		puts("B");
	else 
		puts("C");
}

int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	
	prework();
	
	scanf("%d",&T);
	while(T--)
		mian();

	return 0;
}

T3 学习 LIS

有一个长度为 \(n\) 的序列 \(a\),满足 \(a_i\in [1,m]\)\(a_i\in \mathbb{N^*}\)。对 \(a\) 求最长上升子序列,记 \(f_i\) 为以 \(i\) 结尾的 LIS 的长度。

现在给定 \(f\) 数组,求有多少序列 \(a\) 满足条件

\(1\leq n\leq 20,\,1\leq m\leq 3000\)

很难

首先 \(n\) 一眼状压。我们发现,我们只关注原序列到底有多少种不同的数字以及它们的相对关系,并不关注出现了什么。所以我们定义“单位序列”为其中元素是从 \(1\) 开始连续的一段的序列,如 \(\{1,2,1,3\}\),它可以用来代表 \(\{1,4,1,6\},\{1,5,1,8\}\) 等。我们只需计算“单位序列”的方案数,再乘上组合数即可

考虑计算方案,设 \(f_{i,s}\) 表示填了 \([1,i]\) 的数,填的位置集合为 \(s\) 的方案数。考虑“我为人人”,枚举新填的位置集合 \(s'\),将它们填上 \(i+1\),判断是否合法,合法则贡献上去。枚举子集转移容易做到 \(\mathcal{O}(n^23^n)\)

再把枚举子集的过程拉出来,设 \(f_{i,j,s}\) 表示填了 \([1,i-1]\) 的数,考虑从后往前数第 \(j\) 个位置填不填 \(i\),当前填了的位置集合为 \(s\)。由于我们并无法确定到底有没有使用 \(i\),所以我们求得的实际上是最多用了 \(i\) 个不同的数字的方案数,记为 \(ans_i\)

考虑容斥,则有 \(Ans_i=ans_i-\sum\limits_{j=1}^{i-1}\binom{i}{j}Ans_j\)

预处理一些东西可以做到 \(\mathcal{O}(n^22^n)\)

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=25,M=3010,SS=(1<<20)+10,MOD=998244353;

int n,m,a[N],pre[SS];
int C[M][M],f[2][N][SS],ans[N]; 

void prework()
{
	for(int i=0; i<=m; i++)
		C[i][0]=1;
	for(int i=1; i<=m; i++)
		for(int j=1; j<=i; j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	
	int S=(1<<n)-1;		
	for(int s=0; s<=S; s++)
	{
		for(int i=1; i<=n; i++)
			if(s&(1<<i-1))
				pre[s]=max(pre[s],a[i]);
	}
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
		
	prework();
	
	int S=(1<<n)-1,cur=1,tur=0;
	f[cur][n][0]=1;
	for(int t=1; t<=n; t++)
	{
		for(int i=n; i>=1; i--)
		{
			for(int s=0; s<=S; s++)
			{
				if(f[cur][i][s])
				{
					int tmp=f[cur][i][s];
					if(i>1)
						(f[cur][i-1][s]+=tmp)%=MOD;
					else
						(f[tur][n][s]+=tmp)%=MOD;
					if(!(s&(1<<i-1)) && pre[s&((1<<i-1)-1)]+1==a[i])
					{
						if(i>1)
							(f[cur][i-1][s|(1<<i-1)]+=tmp)%=MOD;
						else
							(f[tur][n][s|(1<<i-1)]+=tmp)%=MOD;
					} 
					f[cur][i][s]=0;
				}
			}
		}
		ans[t]=f[tur][n][S];
		swap(cur,tur);
	}
	
	int sum=0;
	for(int i=1; i<=n; i++)
	{
		for(int j=i-1; j>=1; j--)
			(ans[i]+=(1LL*(-1)*C[i][j]*ans[j]%MOD+MOD))%=MOD;
		(sum+=1LL*C[m][i]*ans[i]%MOD)%=MOD;
	}
	
	printf("%d",sum);

	return 0;
}

2023.10.16

T1 智乃的差分

2023.10.17

计数场

T1 序列计数

\(n\) 头奶牛,每头奶牛的品种 \(a_i\in\{'A'\sim 'J'\}\),现在想要从中选若干头奶牛出来拍照(不改变顺序)

定义一种照片的混乱度为照片中相邻但种类不同的奶牛的对数。若将一张照片中的所有奶牛任意改变顺序,而原图的混乱度仍是最小(或之一)的话,则称这张照片是“好的”,求共能拍出多少张“好的照片

\(T\leq 5,\,n\leq 1000\)

签到题,只拿了 60 分……

一开始设 \(f_{i,s}\) 表示以第 \(i\) 头奶牛为结尾,选的品种集合为 \(s\) 的方案数,转移是 \(\mathcal{O}(n)\) 的,总复杂度 \(\mathcal{O}(n^22^{10})\)

果然还是状态设的不够好,设 \(f_{i,s,k}\) 表示考虑到第 \(i\) 位,选的品种集合为 \(s\),最后一位颜色为 \(k\) 的方案数

则有 \(f_{i,s,k}=\begin{cases}\sum\limits_{j\ne a_i}f_{i-1,s',j}+2\times f_{i-1,s,k}&a_i=k\\f_{i-1,s,k}&a_i\ne k\end{cases}\)

滚个前缀和优化即可,空间很小要用滚动数组

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=1010,M=(1<<10)+10,MOD=998244353;

bool Mbe;

int T,n,a[N],tot,t[N][15],f[2][M][15],g[2][M];
char s[N];
map <char,int> id;

void init()
{
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	tot=0;
	id.clear();
}

void mian()
{
	init();
	
	scanf("%d%s",&n,s+1);
	for(int i=1; i<=n; i++)
	{
		if(id.find(s[i])==id.end())
		{
			id[s[i]]=++tot;
			a[i]=tot;
		}
		else
			a[i]=id[s[i]];
	} 
	
	f[0][0][0]=g[0][0]=1;  int S=(1<<tot)-1;
	for(int i=1; i<=n; i++)
	{
		for(int s=0; s<=S; s++)
		{
			g[i&1][s]=0;
			for(int k=1; k<=tot; k++)
			{
				f[i&1][s][k]=0;
				if(!(s&(1<<k-1)))
					continue;
				if(a[i]==k)
					(f[i&1][s][k]+=g[(i&1)^1][s^(1<<k-1)]+f[(i&1)^1][s][k])%=MOD;
				(f[i&1][s][k]+=f[(i&1)^1][s][k])%=MOD;
				(g[i&1][s]+=f[i&1][s][k])%=MOD;
			}
			
//			cout<<i<<" "<<s<<" "<<g[i][s]<<endl;
		}
		f[i&1][0][0]=g[i&1][0]=1;
	}
	
	int ans=0;
	for(int s=1; s<=S; s++)
		(ans+=g[n&1][s])%=MOD;
	
//	int ans=0;
//	for(int s=1; s<=S; s++)
//		(ans+=f[s][n])%=MOD;//,cout<<s<<" "<<f[s][n]<<endl;
	printf("%d\n",ans);
}

bool Med;

int main()
{
	freopen("counta.in","r",stdin);
	freopen("counta.out","w",stdout);
	
//	fprintf(stderr,"%.3lfMB\n",(&Mbe-&Med)/1048576.0);
	
	scanf("%d",&T);
	while(T--)
		mian();	 

	return 0;
}

T2 子段计数

CF1736C2 Good Subarrays (Hard Version)

再也不写线段树二分了!!!!!!

\(a_l\) 表示以 \(l\) 为左端点最远能到达的点,则有 \(\max\limits_{i=l}^{a_l}\{t_i-i\}+l>0\),移项得 \(l>i-t_i\),这东西可以 \(\mathcal{O}(n\log^2 n)\) 二分预处理出来,所以初始的答案可以计算出来,那我们只需考虑 \(\Delta ans\)

对于操作 \((x,v)\),分三种情况讨论:

  • \(t_x=v\):此时 \(\Delta =0\)

  • \(t_x>v\):这时会有一段区间越不过 \(x\) 了,将对应贡献减去即可,是简单的

  • \(t_x<v\):此时会有原先一段越不过 \(x\) 的区间越过了 \(x\)。考虑预处理一个数组 \(b_l\) 表示在忽略 \(a_l+1\) 的情况下以 \(l\) 为左端点最远能到达哪里,这也是好预处理的。\(\Delta\)\(\sum b-\sum a\)

显然你用兔队线段树也可以

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10,INF=1e18;

int n,t[N],a[N],b[N],m;
LL ans,sum[N],sum2[N];

#define lc(p) p<<1
#define rc(p) p<<1|1
struct SegmentTree
{
	LL mx;
	#define mx(x) tree[x].mx 
}tree[N<<2];

void pushup(int p)
{
	mx(p)=max(mx(lc(p)),mx(rc(p)));
}

void build(int p,int l,int r)
{
	mx(p)=-INF;
	if(l==r)
	{
		mx(p)=(LL)a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	pushup(p);
}

LL ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l && qr>=r)
		return mx(p);
	int mid=(l+r)>>1;
	LL res=-INF;
	if(ql<=mid)
		res=max(res,ask(lc(p),l,mid,ql,qr));
	if(qr>mid)
		res=max(res,ask(rc(p),mid+1,r,ql,qr));
	return res;
}

int find(int l,int r,int v)
{
	int lo=l,hi=r,res=lo-1;
	while(lo<=hi)
	{
		int mid=(lo+hi)>>1;
		if(ask(1,1,n,lo,mid)<v)
			lo=mid+1,res=mid;
		else
			hi=mid-1;
	}
	return res;
}

int find2(int l,int r,int v)
{
	int lo=l-1,hi=r+1;
	while(lo+1<hi)
	{
		int mid=(lo+hi)>>1;
		if(a[mid]>=v)
			hi=mid;
		else
			lo=mid;
	}
	if(a[lo]>=v && lo!=0)
		return lo;
	return hi;
}

LL get(int l,int r)
{
	LL res1=1LL*l*(l-1)/2;
	LL res2=1LL*(1+r)*r/2;
	return res2-res1;
}

int main()
{
// 	freopen("countb4.in","r",stdin);
// 	freopen("countb.out","w",stdout);

	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&t[i]),a[i]=i-t[i];
	
	build(1,1,n);
	
	for(int i=1; i<=n; i++)
	{
		a[i]=find(i,n,i);
		ans+=1LL*(a[i]-i+1);
		sum[i]=sum[i-1]+1LL*(a[i]-i+1);
		if(a[i]==n)
			b[i]=n;
		else if(a[i]==n-1)
			b[i]=n;
		else
		{
			b[i]=find(a[i]+2,n,i);
			if(b[i]>n)	
				b[i]=n;
		}
		sum2[i]=sum2[i-1]+1LL*(b[i]-i+1);
	}
	
	scanf("%d",&m);
	while(m--)
	{
		int x,v;
		scanf("%d%d",&x,&v);
		
		if(t[x]==v)
		{
			printf("%lld\n",ans);
			continue;
		}
		
		int pos=find2(1,x,x),l=max(x-v,0);
		if(t[x]>v)
		{
			if(l<pos)
			{
				printf("%lld\n",ans);
				continue;
			}
			LL pre=sum[l]-sum[pos-1];
			LL cur=1LL*(l-pos+1)*x-get(pos,l);
			LL res=ans-pre+cur;	
			printf("%lld\n",res);
		}
		else
		{
			int pos2=find2(1,x-1,x-1);
			if(pos2<=l)
				pos2=l+1;
			LL res=ans-sum[pos-1]+sum[pos2-1];
			res=res+sum2[pos-1]-sum2[pos2-1];
			printf("%lld\n",res);
		}
	}

	return 0;
}

T3 组合计数

CF1307E Cow and Treats 的加强版,\(n,m\leq 10^5\)

先讨论弱化版

预处理 \(L_i,R_i\) 表示第 \(i\) 头奶牛从左/从右走会在哪个草停下,若不会停下即吃不饱则不考虑

考虑枚举第一头从左往右走的奶牛 \(p\),它将会在 \(L_p\) 处停下,此时草被划分成 \([1,L_p)\)\((L_p,n]\) 两个区间,后面的奶牛都不能越过断点

显然一种颜色的奶牛最多只能有 \(2\) 头。又因为具体的顺序无关紧要,所以我们可以分不同颜色来考虑,对每种颜色的奶牛计算三个数 \(s,sl,sr\),分别表示

  • \(s\)\(L_x<L_p,R_x>R_p\) 的个数

  • \(sl\)\(L_x<L_p,R_x\leq R_p\) 的个数

  • \(sr\)\(L_x\geq L_p,R_x>R_p\) 的个数

容易发现 \(sl,sr\) 不能同时大于 \(0\)

对于 \(f_x\ne f_p\) 的情况来说:

  • \(s\ge 2\) 或者 \(s=1\)\(sl,sr\) 不都为 \(0\),则可以有两头奶牛,方案数为 \(s\times (s-1+sl+sr)\)

  • 否则,若 \(s,sl,sr\) 不都为 \(0\),则可以有一头奶牛,方案数为 \(2\times s+sl+sr\)

对于 \(f_x=f_p\) 的情况来说:
显然 \(x\) 只能从右边走,若 \(s,sr\) 不都为 \(0\),则可以有一头奶牛,方案数为 \(s+sr\)

时间复杂度 \(\mathcal{O}(m^2)\),可以通过弱化版

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=5010,MOD=1e9+7;

int n,m,c[N],f[N],h[N],L[N],R[N],sum[N];
vector <int> col[N],cow[N];

int calc(int x,int p)
{
	int cnt=(x>0),res=1;
	for(int i=1; i<=n; i++)
	{
		int s=0,sl=0,sr=0;
		for(int j:cow[i])
		{
			if(j==x)
				continue;
			if(L[j]<p && R[j]>p)
				s++;
			else if(L[j]<p)
				sl++;
			else if(R[j]>p)
				sr++;
		}
		
		if(f[x]==i)
		{
			if(s+sr>=1)
				cnt++,res=1LL*res*(s+sr)%MOD;
		}	
		else if((s>=2) || (s==1 && sl+sr>=1))
			cnt+=2,res=1LL*res*s%MOD*(s-1+sl+sr)%MOD;
		else if(s+sl+sr>=1)
			cnt++,res=1LL*res*(2*s+sl+sr)%MOD;
	}
	(sum[cnt]+=res)%=MOD;
	return cnt;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&c[i]),col[c[i]].push_back(i);
	for(int i=1; i<=m; i++)
	{
		scanf("%d%d",&f[i],&h[i]);
		if(h[i]>col[f[i]].size())
			continue;
		L[i]=col[f[i]][h[i]-1];
		R[i]=col[f[i]][col[f[i]].size()-h[i]];
		cow[f[i]].push_back(i);
	}
	
	int ans=calc(0,0);
	for(int i=1; i<=n; i++)
		for(int j:cow[i])
			ans=max(ans,calc(j,L[j]));
	printf("%d %d",ans,sum[ans]);

	return 0;
}

再来考虑强化版

我们发现每次枚举 \(p\) 会有大量重复计算,考虑对于每个 \(l_p=i\)\(p\) 只枚举一次,也就是说,对于每个草我们都只枚举一次

我们可以先让所有奶牛从右边走,可以计算出此时的奶牛数和方案数。之后,将断点右移,对于 \(f_x=c_i\) 的奶牛 \(x\),重新计算贡献。在下次移动断点前,将每一头奶牛可以走的方向更新一下

具体来说,设 \(dir_i=0/1/2/3\) 表示第 \(i\) 头奶牛的方向,\(s_{x,d}\) 表示第 \(x\) 种颜色的奶牛中方向为 \(d\) 的个数,初始化 \(dir_i=2,s_{f_i,2}++\)。再对于每种颜色分别计算,可以算出初始的答案,记 \(val_i\) 为第 \(i\) 种颜色的奶牛最优情况下的方案数

之后,从左到右枚举断点,先将当前颜色的贡献减去,再重新计算

最后,将 \(L_x=i\)\(R_x=i+1\) 的奶牛的方向更新一下

时间复杂度 \(\mathcal{O}(n\log P)\),瓶颈在于求逆元,可以扫两遍做到 \(\mathcal{O}(n)\),但没必要

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=1e5+10,MOD=1e9+7;

int T,n,m,c[N],f[N],h[N],L[N],R[N];
int cnt,cur,s[N][5],dir[N],val[N],sum[N],ans;
vector <int> col[N],posl[N],posr[N];
bool vis[N];

void init()
{
	for(int i=1; i<=n; i++)
		posl[i].clear(),posr[i].clear(),col[i].clear();
	memset(vis,0,sizeof(vis));
	memset(s,0,sizeof(s));
	memset(sum,0,sizeof(sum));
}

int ksm(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1)
			res=1LL*res*x%MOD;
		x=1LL*x*x%MOD;
		y>>=1; 
	}
	return res;
}

int get(int x)
{
	if(s[x][3]>=2 || (s[x][3]==1 && s[x][1]+s[x][2]>=1))
		return 2;
	if(s[x][3]+s[x][2]+s[x][1]>=1)
		return 1;
	return 0;
}

void add(int x,int y)
{
	int cc=f[x],tmp=get(cc),res=1;
	cnt+=y*tmp;
	if(tmp==2)
		res=1LL*s[cc][3]*(s[cc][3]-1+s[cc][2]+s[cc][1])%MOD;
	else if(tmp==1)
		res=1LL*(2*s[cc][3]+s[cc][2]+s[cc][1])%MOD;
	if(y==-1)
		res=ksm(res,MOD-2);
	val[cc]=1LL*val[cc]*res%MOD;  
	cur=1LL*cur*res%MOD;
}

void update(int x,int y)
{
	add(x,-1);  s[f[x]][dir[x]]--;
	dir[x]=y;
	s[f[x]][dir[x]]++;  add(x,1);
}

void mian()
{
	init();
	
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&c[i]),col[c[i]].push_back(i),val[i]=1;
	for(int i=1; i<=m; i++)
	{
		scanf("%d%d",&f[i],&h[i]);
		if(h[i]>col[f[i]].size())
			continue;
		L[i]=col[f[i]][h[i]-1];
		R[i]=col[f[i]][col[f[i]].size()-h[i]];
		posl[L[i]].push_back(i);
		posr[R[i]].push_back(i);
		s[f[i]][dir[i]=2]++;
	}
	
	cnt=0;  cur=1;
	for(int i=1; i<=m; i++)
		if(!vis[f[i]])
			vis[f[i]]=1,add(i,1);
	sum[ans=cnt]=cur;
	
	for(int i=0; i<=n; i++)
	{
		int c=::c[i];
		if(posl[i].size())
		{
			int x=posl[i][0],delta=s[c][3]+s[c][2]-(R[x]>i);
			cnt-=get(c);
			cur=1LL*cur*ksm(val[c],MOD-2)%MOD;
			int nowcnt=cnt+1+(delta>0),deltacur=max(1,delta);
			ans=max(ans,nowcnt);
			(sum[nowcnt]+=1LL*cur*deltacur%MOD)%=MOD;
			cnt+=get(c);
			cur=1LL*cur*val[c]%MOD;
		}
		for(int x:posl[i])
			update(x,dir[x]^1);
		for(int x:posr[i+1])
			update(x,dir[x]^2);
	}
	
	printf("%d %d\n",ans,sum[ans]);
}

int main()
{
	freopen("countc.in","r",stdin);
	freopen("countc.out","w",stdout);
	
	scanf("%d",&T);
	while(T--)
		mian();

	return 0;
}
posted @ 2023-10-16 21:35  xishanmeigao  阅读(64)  评论(0)    收藏  举报