2023.10.11~2023.10.17测试

NOIP模拟赛 2023.10.11~2023.10.17

2023.10.11

T1 染色

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

1n10000

诈骗题

观察到若 j=i+4i,j 可同色,所以答案上界为 4,而样例中 n=7 的答案已经是 4,所以 n7 的全都是 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,需满足 0bini=1maibiDdi 为整数

i=1mbi+kmin{bi} 的最大值

1T51n1091k,m2×1051D10181ai5000

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

从贪心来说显然是先考虑小的 ai,将 a 从小到大排序,令 bm=mn 并让 b1bm1 也先填上 mn。接下来要想最大化 bi,显然是填的 n 越多越好(当然也可以证明这样是最优的),所以考虑二分出最后一个填 n 的位置 pos,那么再令 bpos+1 填一个最大能填的数即可

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

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

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 沿着 lr 的简单路径走 k 步恰好走到 k

1n,m3×105

简单题,但是树上倍增预处理写错了 1005

将一条 lr 的简单路径转化成 llcar,对于 llca 这条路径上的点,需满足 depldepx=xdepx+x=depl。对于 lcar 这条路径上的点,需满足 depldeplca+depxdeplca=xdepxx=2×deplcadepl。树上差分+主席树维护即可

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,m2×105

fi 表示 i 往右第一次出现 ai 的位置,那么一个区间 [l,r] 的,当且仅当 r<mini=lr{fi}

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

i=lrmin{minj=ir{fj},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 维护 fi

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×n 的矩阵 A,初始所有元素均为 0

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

n103q3×105

就是给一个等腰直角三角形全部加上 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={a1,a2,,an},每轮操作有两种选择,保留 A 中所有 bi 的倍数,或者保留 A 中所有非 bi 的倍数。

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

1n2×1041m2×1051014ai10141bi1014

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

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

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

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 个点的图,点权 gcd 为合数的点之间连边。求一个点后剩下的最大联通块的最小值

T10n105ai105

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

暴力来说建边和计算都是 O(n2)

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

对于计算,首先观察可发现肯定是割掉最大联通块里的一个割点,最后再计算最大联通块。割点用 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×m 的矩阵 AAi,j{1,2,3}。每次可以任意交换两行,问能否使每列单调不降

T,n100

签到题,写了 1.5h,纯唐

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)mod3

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

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

x=(n1i1),因为 x 很大,所以欧拉降幂得 aixmodφ(7)=aixmod6。所以即求 (n1i1)mod6。由于 6 不是质数,所以不能直接用 Lucas 定理,但是我们可以先对 23Lucas 定理,再用中国剩余定理求出来

代码不难写

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,满足 ai[1,m]aiN。对 a 求最长上升子序列,记 fi 为以 i 结尾的 LIS 的长度。

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

1n20,1m3000

很难

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

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

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

考虑容斥,则有 Ansi=ansij=1i1(ij)Ansj

预处理一些东西可以做到 O(n22n)

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 头奶牛,每头奶牛的品种 ai{AJ},现在想要从中选若干头奶牛出来拍照(不改变顺序)

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

T5,n1000

签到题,只拿了 60 分……

一开始设 fi,s 表示以第 i 头奶牛为结尾,选的品种集合为 s 的方案数,转移是 O(n) 的,总复杂度 O(n2210)

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

则有 fi,s,k={jaifi1,s,j+2×fi1,s,kai=kfi1,s,kaik

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

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)

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

al 表示以 l 为左端点最远能到达的点,则有 maxi=lal{tii}+l>0,移项得 l>iti,这东西可以 O(nlog2n) 二分预处理出来,所以初始的答案可以计算出来,那我们只需考虑 Δans

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

  • tx=v:此时 Δ=0

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

  • tx<v:此时会有原先一段越不过 x 的区间越过了 x。考虑预处理一个数组 bl 表示在忽略 al+1 的情况下以 l 为左端点最远能到达哪里,这也是好预处理的。Δba

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

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,m105

先讨论弱化版

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

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

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

  • sLx<Lp,Rx>Rp 的个数

  • slLx<Lp,RxRp 的个数

  • srLxLp,Rx>Rp 的个数

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

对于 fxfp 的情况来说:

  • s2 或者 s=1sl,sr 不都为 0,则可以有两头奶牛,方案数为 s×(s1+sl+sr)

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

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

时间复杂度 O(m2),可以通过弱化版

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 会有大量重复计算,考虑对于每个 lp=ip 只枚举一次,也就是说,对于每个草我们都只枚举一次

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

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

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

最后,将 Lx=iRx=i+1 的奶牛的方向更新一下

时间复杂度 O(nlogP),瓶颈在于求逆元,可以扫两遍做到 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 @   xishanmeigao  阅读(42)  评论(0编辑  收藏  举报
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示