联合省选 2020 - 2021 选做(A)


选做(指扔掉我完全不明所以的(我眼中的)大难题)

目前进度:

  • 2020:4/6
  • 2021:4/6

如果有错误的话,求指出……

2020

D1T1 冰火战士

容易发现答案是对两个数组分别做前缀和和后缀和,从场地温度处劈开取 \(\min\)。这两个前缀和是一个单增函数和一个单减函数,最优决策点只可能存在于交点的两侧。

树状数组维护前缀和,查询时倍增即可。

代码(几个月前写的):

#include
#include
using namespace std;
const int N=2000086;
int q,fc,nval,rval[N],c[2][N];
struct ques{
	int opt,t,x,y;
}que[N];
struct fi{
	int temp,ener,id;
	fi(){}
	fi(int tt,int ee,int idd){temp=tt;ener=ee;id=idd;}
	bool operator <(fi b)const{return tempb?a:b;}
int Min(int a,int b){return a'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
int lowbit(int x){return x&(-x);}
bool updata(int pos,int num,int flr){
	if(!pos||pos>nval)return false;
	while(pos<=nval)c[flr][pos]+=num,pos+=lowbit(pos);
	return true;
}
int query(int pos,int flr){
	int ret=0;
	while(pos)ret+=c[flr][pos],pos-=lowbit(pos);
	return ret;
}
int getlog(int x){
	int ret=0;while(x)x>>=1,ret++;
	return ret;
}
int main(){
//	freopen("icefire2.in","r",stdin);
//	freopen("icefire2.out","w",stdout);
	q=read();
	for(int i=1;i<=q;i++){
		que[i].opt=read();que[i].t=read();
		if(que[i].opt&1)que[i].x=read(),que[i].y=read(),sd[++fc]=fi(que[i].x,que[i].y,i);
	}
	sort(sd+1,sd+fc+1);
	for(int i=1;i<=fc;i++){
		nval+=(i==1||sd[i].temp>sd[i-1].temp);
		rval[nval]=sd[i].temp;que[sd[i].id].x=nval;
	}
	int lg=getlog(nval),tot=0;
	for(int i=1;i<=q;i++){
		if(que[i].opt&1){
			if(!que[i].t)updata(que[i].x,que[i].y,0);
			else updata(que[i].x+1,-que[i].y,1),tot+=que[i].y;
		}
		else{
			int rid=que[i].t;
			if(!que[rid].t)updata(que[rid].x,-que[rid].y,0);
			else updata(que[rid].x+1,que[rid].y,1),tot-=que[rid].y;
		}
		int c1=tot,c0=0,np=0,ans=0;
		for(int j=lg;j>=0;j--){
			if(np+(1<nval)continue;
			if(c1+c[1][np+(1<=c0+c[0][np+(1<=ans){
				ans=bak*2;c1=tot,c0=0,np=0;
				for(int j=lg;j>=0;j--){
					if(np+(1<nval)continue;
					if(c1+c[1][np+(1<=c0+c[0][np+(1<

比较奇妙的是这份代码本地一个点能跑七八秒,交上去却能过。

D1T2 组合数问题

只会比较死板的套公式证明方法……题解区说的牛逼做法我一个都不会……

标红色的是推出下一步需要处理掉的部分

\[\begin{aligned} \\&\sum\limits^n_{k=0}\color{red}{f(k)}x^k\binom{n}{k} \\=&\sum\limits^m_{i=0}a_i\sum\limits^n_{k=0}\color{red}{k^i}x^k\binom{n}{k} \\=&\sum\limits^m_{i=0}a_i\sum\limits^n_{k=0}\color{red}{\sum\limits^i_{j=0}j!{i\brace j}}\binom{k}{j}\binom{n}{k}x^k \\=&\sum\limits^m_{i=0}a_i\sum\limits^i_{j=0}j!{i\brace j}\sum\limits^n_{k=0}\color{red}{\binom{k}{j}\binom{n}{k}}x^k \\=&\sum\limits^m_{i=0}a_i\sum\limits^i_{j=0}\color{red}{j!}{i\brace j}\sum\limits^n_{k=0}\color{red}{\binom{n}{j}}\binom{n-j}{k-j}x^k \\=&\sum\limits^m_{i=0}a_i\sum\limits^i_{j=0}{i\brace j}n^\underline j\color{red}{\sum\limits^n_{k=0}\binom{n-j}{k-j}x^k} \\=&\sum\limits^m_{i=0}a_i\sum\limits^i_{j=0}{i\brace j}n^\underline jx^j(1+x)^{n-j} \end{aligned} \]

然后预处理一下需要处理的东西就做完啦。

代码:

#include
const int M=1145;
int n,x,MOD,ans,m,a[M],g[M],pw[M],s[M][M];
int Add(int &a,int b){return(a+=b)>=MOD?a-=MOD:a;}
int vAdd(int a,int b){return(a+=b)>=MOD?a-=MOD:a;}
int Sub(int &a,int b){return(a-=b)<0?a+=MOD:a;}
int vSub(int a,int b){return(a-=b)<0?a+=MOD:a;}
int Mul(int a,int b){return 1ll*a*b%MOD;}
int qpow(int a,int b){
	int ret=1;
	while(b){if(b&1)ret=Mul(ret,a);a=Mul(a,a);b>>=1;}
	return ret;
}
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
int main(){
	n=read();x=read();MOD=read();m=read();
	for(int i=0;i<=m;i++)a[i]=read();
	s[0][0]=1;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=i;j++)
			s[i][j]=vAdd(s[i-1][j-1],Mul(s[i-1][j],j));
	g[0]=1;for(int i=1;i<=m;i++)g[i]=Mul(g[i-1],n-i+1);
	for(int j=0;j<=m;j++)pw[j]=Mul(qpow(x,j),qpow(vAdd(1,x),n-j));
	for(int i=0;i<=m;i++){
		int nans=0;
		for(int j=0;j<=i;j++)
			Add(nans,Mul(s[i][j],Mul(g[j],pw[j])));
		Add(ans,Mul(nans,a[i]));
	}
	printf("%d",ans);
}

D1T3 魔法商店

那我显然不会

D2T1 信号传递

\(f_S\) 表示从左到右放置 \(S\) 集合内的点时对答案的贡献,需要用到一点费用提前计算的思想。

然后分别设 \(g_{x,S}\)\(h_{x_s}\) 表示 \(S\) 集合内的点向 \(x\) 传递信息的次数和 \(x\)\(s\) 集合传递的次数。设 \(t_S\) 表示 \(S\) 集合向外传递的次数,这个利用 \(g\)\(h\) 容易求出。

有了这些就可以直接 DP 了。这题难点好像在卡空间上面,对 \(h\)\(g\) 折半处理即可。

#include
#include
using namespace std;
const int N=23,M=12,L=100086;
int n,m,k,U,sz1,sz2;
int cnt[(1<'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
int gp(int x){if(x>sz1)return(1<<(x-sz1-1));return 1<<(x-1);}
int G(int x,int S){
	int s1=S&((1<>sz1)&((1<>sz1)&((1<>1;sz2=m-sz1;int lst=read();
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=m;i++)lg[1<<(i-1)]=i;
	for(int i=2;i<=n;i++){
		int np=read();if(np==lst)continue;
		g[lst>sz1][np][gp(lst)]++;
		h[np>sz1][lst][gp(np)]++;
		t[1<<(lst-1)]++;lst=np;
	}
	for(int i=1;i<=m;i++)
		for(int S=1;S<(1<

D2T2 树

我不会,呜呜

D2T3 作业题

一个柿子:\(\gcd\{A\}=\sum[\forall i,d|a_i]\phi(d)\)

因此枚举 \(d\),按照套路利用形式幂级数和矩阵树算边权和即可。

剪枝:忽略可选边数 \(<(n-1)\)\(d\),这样复杂度就对了。

#include
#include
using std::vector;
const int N=33,V=152510,MOD=998244353;
int n,m,ans,mc,eu[N*N],ev[N*N],ew[N*N],pc,v[V],p[V],phi[V],exi[V],num[V],cnt[V];
vectorvec[N*N];
int Add(int &a,int b){return(a+=b)>=MOD?a-=MOD:a;}
int Sub(int &a,int b){return(a-=b)<0?a+=MOD:a;}
int qpow(int a,int b){
	int ret=1;
	while(b){if(b&1)ret=1ll*ret*a%MOD;a=1ll*a*a%MOD;b>>=1;}
	return ret;
}
struct Poly{
	int a[2];
	Poly(){a[0]=a[1]=0;}
	Poly(int x,int y){a[0]=x;a[1]=y;}
	int& operator [](const int x){return a[x];}
	Poly operator +(Poly b){return Poly((a[0]+b[0])%MOD,(a[1]+b[1])%MOD);}
	Poly operator -(Poly b){return Poly((a[0]-b[0]+MOD)%MOD,(a[1]-b[1]+MOD)%MOD);}
	Poly operator *(Poly b){return Poly((1ll*a[0]*b[0]%MOD),(1ll*a[0]*b[1]%MOD+1ll*a[1]*b[0]%MOD)%MOD);}
	Poly Inv(){int x=qpow(a[0],MOD-2);return Poly(x,1ll*(MOD-1ll*a[1]*x%MOD)*x%MOD);}
	bool Swap(Poly b){Poly t=*this;a[0]=b[0];a[1]=b[1];b=t;return true;}
};
struct Matrix{
	Poly a[N][N];int sze;
	Matrix(){sze=0;}
	Matrix(int x){
		sze=x;
		for(int i=1;i<=x;i++)
			for(int j=1;j<=x;j++)
				a[i][j]=Poly();
	}
	Poly* operator [](const int x){return a[x];}
	int det(){
		Poly ret=Poly(1,0);
		for(int i=1;i'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
bool getphi(){
	phi[1]=v[1]=1;
	for(int i=2;i<=152501;i++){
		if(!v[i])p[++pc]=v[i]=i,phi[i]=i-1;
		for(int j=1;j<=pc;j++){
			if(p[j]>v[i]||p[j]>152501/i)break;
			v[i*p[j]]=p[j];
			phi[i*p[j]]=phi[i]*(p[j]-(i%p[j]>0));
		}
	}
	return true;
}
bool Div(int x,int no){
	for(int i=1;i<=x/i;i++){
		if(x%i)continue;
		vec[no].push_back(i);cnt[i]++;
		if(i*i!=x)vec[no].push_back(x/i),cnt[x/i]++;
	}
	return true;
}
int main(){
	n=read();m=read();getphi();
	for(int i=1;i<=m;i++){eu[i]=read();ev[i]=read();Div(ew[i]=read(),i);}
	for(int i=1;i<=m;i++){
		for(vector::iterator j=vec[i].begin();j!=vec[i].end();j++){
			if(cnt[*j]+1

2021

D1T1 卡牌游戏

\(a\)\(b\) 一起排序记来源,双指针扫过去。

#include
#include
using namespace std;
const int N=1000086;
int n,m,vis[N];
struct asdf{
	int val,pos,typ;
	asdf(){}
	asdf(int v,int p,int t){val=v;pos=p;typ=t;}
	bool operator <(const asdf b)const{return valb?a:b;}
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++)a[i]=asdf(read(),i,1);
	for(int i=1;i<=n;i++)a[n+i]=asdf(read(),i,2);
	int ans=a[n].val-a[1].val;
	sort(a+1,a+n+n+1);
	int lp=1,cnt=0,c2=0;
	for(int i=1;i<=2*n;i++){
		if(vis[a[i].pos]==0)cnt++;
		if(vis[a[i].pos]==2)c2--;
		vis[a[i].pos]+=a[i].typ;
		if(vis[a[i].pos]==2)c2++;
		while(vis[a[lp].pos]==3&&cnt==n&&c2

D1T2 矩阵游戏

我不会,呜呜

D1T3 图函数

有个不怎么难想的结论是有序点对 \((i,j)\) 在一张图上能产生贡献当且仅当它们在同一个环上,并且这个环上所有的点都大于等于 \(i,j\) 中较小的一个。

所以我们对每一个点对找它们所在的所有这样的环中最小边权最大的一个,这样一来它们的贡献就很好算了。

于是有个显然的 \(O(n^3)\) Floyd 做法……

妈的,讨论区有人说这做法能过,害得我卡了半个下午的常。

张景行有一种 \(O(\frac{n^3+mn}{\omega})\) 的做法,思想还是很好理解的,但代码实现貌似有点困难。

代码(Floyd):

#include
const int N=1001,M=200010;
int n,m,e[N][N],ans[M];
inline int Min(int a,int b){return ab?a:b;}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)e[i][i]=m+1;
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		e[x][y]=i;
	}
	for(int k=n;k>=1;k--){
		for(int i=k;i<=n;i++)
			ans[Min(e[i][k],e[k][i])]++;
		for(int i=1;i<=k;i++)
			if(e[i][k])
				for(int j=1;j<=n;j++)
					e[i][j]=Max(e[i][j],Min(e[i][k],e[k][j]));
		for(int i=k+1;i<=n;i++)
			if(e[i][k])
				for(int j=1;j=1;i--)ans[i]+=ans[i+1];
	for(int i=1;i<=m+1;i++)printf("%d",ans[i]),putchar(' ');
	return 0;
}

D2T1 宝石

个人感觉今年省选题中最简单的一道啦。

倍增预处理从每个位置向上正 / 倒序收集宝石的情况,询问离线后挂在起点和终点上。

从起点倍增到 LCA,再从终点向上倍增(或者二分)套倍增即可。

存在一枚 \(\log\) 的点分做法,但是我做这道题已经是八个月以前的事情了,当时好像还不会点分。

代码(八个月前写的,比较丑陋):

#include
#include
using namespace std;
int n,tot,tq,m,c,q,u,v,s,t;
int fa[200086][18],depth[200086],lsg[200086][16],nsg[200086][16];
int lgem[50086],no[50086],w[200086],beg[200086],h[200086],hq[200086],p[200086],lg[200086],ans[200086];
struct edge{
	int v,nxt;
}e[400010];
struct qry{
	int v,no,nxt;
}que[200010];
int read(){
	char ch=getchar();int nn=0,ssss=1;
	while(ch<'0'||ch>'9'){if(ch=='-')ssss*=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){nn=nn*10+(ch-'0');ch=getchar();}
	return nn*ssss; 
}
bool add(int u,int v){
	e[++tot].v=v;
	e[tot].nxt=h[u];
	h[u]=tot;
	return true;
}
bool addq(int s,int t,int no){
	que[++tq].v=s;
	que[tq].nxt=hq[t];
	que[tq].no=no;
	hq[t]=tq;
	return true;
}
bool pre_dfs(int np,int lst){
	int tmp=lgem[no[w[np]]];
	lgem[no[w[np]]]=np;
	beg[np]=lgem[1];
	if(no[w[np]])lsg[np][0]=lgem[no[w[np]]-1];
	if(no[w[np]])nsg[np][0]=lgem[no[w[np]]+1];
	fa[np][0]=lst;depth[np]=depth[lst]+1;
	for(int i=1;(1<depth[y])x=fa[x][lg[depth[x]-depth[y]]-1];
	if(x==y)return x;
	for(int i=lg[depth[x]]-1;i>=0;i--)
		if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int getnxt(int pos,int lim){
	if(depth[beg[pos]]=0;i--)
		if(depth[nsg[pos][i]]>=lim)pos=nsg[pos][i];
	return no[w[pos]];
}
bool check(int pos,int gc,int lim){
	if(depth[pos]=0;i--){
		if(np-(1<=0;i--)
		if(gc+ng+(1<

D2T2 滚榜

又是神必状压。考虑到我们只关心排名而并不关心每个队伍具体过题数,可以预处理出 \(f_{i,j}\) 表示 \(i\) 队伍需要在 \(j\) 队伍之后被滚到榜首的话需要多过几道题,然后和去年那道状压一样利用费用提前计算的思想,跑一个类似背包的 DP 即可。

好像有很牛逼的折半搜索,但我不会……

#include
int n,m,a[15],c[15][15],lg[8200],bitc[8200];
long long f[8200][15][512];
int Max(int a,int b){return a>b?a:b;}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		lg[1<<(i-1)]=i;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			c[i][j]=Max(0,a[i]-a[j]+(i=c[lp][pos]*lb;i--)
					f[S][pos][i]+=f[ss][lp][i-c[lp][pos]*lb];
			}
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			ans+=f[(1<

D2T3 支配

\[\Huge\color{red}{\textsf{支 配}} \]

害怕.jpg