7&8月CWOI杂题

C0281 【0704 B组】模拟测试

有一说一,这场挺简单。

A 【0630 BC组】棍子

二分。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
using namespace std;
const int inf=1e18; 
const db eps=1e-9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,c,k,a[55];
int check(db mid){
	if(mid==0)return 1;
	int cnt=0,num=c;
	for(int i=1;i<=n;i++){
		if(a[i]<mid)continue;
		if(num){
			int t=min(num,(int)(a[i]/mid)-1);
			num-=t,cnt+=t+1;
		}
		else cnt++;
	}
	return (cnt>=k);
}
signed main(){
	n=read(),c=read(),k=read();
	for(int i=1;i<=n;i++)a[i]=read();
	db l=0,r=1000000000,res=0;
	for(int i=1;i<=200;i++){
		db mid=(l+r)/2.0;
		if(check(mid))res=mid,l=mid;
		else r=mid;
	}
	printf("%.10lf",res);
	return 0;
}

B 【0630 BC组】数组

如果你枚举 \(a\),那么 \(b\) 的取值就是数轴上 \(\max(\lceil\frac{lc}{a}\rceil,lb)\)\(\min(\lfloor\frac{rc}{a}\rfloor,rb)\) 这一段(可能会左端点大于右端点,需要对 0 取个 max)。\(\lceil\frac{lc}{a}\rceil\) 写成 \(\lfloor\frac{lc-1}{a}\rfloor+1\) 整除分块即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
signed main(){
	int la=read(),ra=read(),lb=read(),rb=read(),lc=read(),rc=read(),ans=0;lc--;
	for(int l=la,r;l<=ra;l=r+1){
		r=min(ra,min(((lc/l==0)?ra:lc/(lc/l)),((rc/l==0)?ra:rc/(rc/l))));
		ans+=(r-l+1)*max(min(rc/l,rb)-max(lc/l+1,lb)+1,0ll);
	}
	printf("%lld\n",ans);
	return 0;
}

C 【0630 BC组】十一

鉴定为

根据小奥,我们知道一个数是 11 的倍数,当且仅当它从低往高数第奇数位(你可以认为下文中最低位为第一位)的数码之和,与偶数位的数码之和的差是 11 的倍数。那我们可以预处理出每个数奇数位数码之和与偶数位数码之和的差,这个记作 \(a_i\)。如果这个数最低位放在奇数位,那么它对最终构成的数的贡献是 \(a_i\),反之为 \(-a_i\)

我们考虑先把奇数长度的放好再插入偶数长度的,因为这样新插入的不会影响之前的。定义 \(f_{i,j,k}\) 表示在前 \(i\) 个奇数长度的数中选 \(k\) 个使其贡献为正,总贡献在模 11 意义下为 \(j\) 的方案数。\(g_{i,j,k}\) 是处理偶数长度的,定义类似。

现在考虑插的过程。设奇数长度的有 \(c_0\) 个,偶数长度的有 \(c_1\) 个。枚举奇数长度的总贡献 \(i\),记 \(j=\lfloor\frac{c_0+1}{2}\rfloor\),有 \(k\) 个偶数长度的贡献为正。因为我们是先把奇数长度的放进去,所以贡献是一正一负交替出现的,故只看 dp 部分答案是 \(f_{c_0,i,j}\times g_{c_1,(11-i)\bmod 11,k}\)。容易发现长度奇偶性相同,贡献正负相同的数内部可以自由交换顺序,需要乘上全排列。

最后我们考虑把它们插进去,这个就是一个插板法搞搞的东西,插在正贡献的奇数长度的后面贡献为负,插在正贡献的奇数长度的后面贡献为负。最低位也可以插出正的贡献。

需要特判没有奇数长度的数的情况。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,SIZ=1e5;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int jc[200005],iv[200005],ij[200005]; 
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int safemod(int x,int y){
	return (x%y+y)%y;
}
int a[55],len[55],f[55][15][55],g[55][15][55];char s[55];
signed main(){
	jc[0]=1;for(int i=1;i<=SIZ;i++)jc[i]=jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=SIZ;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=SIZ;i++)ij[i]=ij[i-1]*iv[i]%mod;
	int n=read();
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		int m=strlen(s+1),v=0;
		for(int j=m;j>=1;j--){
			if((m-j)%2==1)v=safemod(v-(s[j]-'0'),11);
			else v=safemod(v+(s[j]-'0'),11);
		}
		a[i]=v,len[i]=m;
	}
	int c0=0,c1=0;f[0][0][0]=g[0][0][0]=1;
	for(int o=1,i=0;o<=n;o++){
		if(len[o]%2==0)continue;
		i++,c0++;
		for(int j=0;j<11;j++){
			for(int k=0;k<=i;k++){
				if(k)f[i][j][k]=safemod(f[i][j][k]+f[i-1][safemod(j-a[o],11)][k-1],mod);
				if(i-k)f[i][j][k]=safemod(f[i][j][k]+f[i-1][safemod(j+a[o],11)][k],mod);
			}
		}
	}
	for(int o=1,i=0;o<=n;o++){
		if(len[o]%2==1)continue;
		i++,c1++;
		for(int j=0;j<11;j++){
			for(int k=0;k<=i;k++){
				if(k)g[i][j][k]=safemod(g[i][j][k]+g[i-1][safemod(j-a[o],11)][k-1],mod);
				if(i-k)g[i][j][k]=safemod(g[i][j][k]+g[i-1][safemod(j+a[o],11)][k],mod);
			}
		}
	}
	if(c0==0){
		printf("%lld\n",jc[c1]*g[c1][0][c1]%mod);
		return 0;		
	}
	int ans=0;
	for(int i=0;i<11;i++){
		for(int j=(c0+1)/2;j<=(c0+1)/2;j++){
			for(int k=0;k<=c1;k++){
				int val=f[c0][i][j]*g[c1][safemod(-i,11)][k]%mod;
				val=val*jc[j]%mod*jc[c0-j]%mod;
				val=val*jc[k]%mod*jc[c1-k]%mod;
				val=val*C(k+(c0-j),(c0-j))%mod;
				val=val*C((c1-k)+j-1,j-1)%mod;
				ans=safemod(ans+val,mod);
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

D 【0630 BC组】棋盘

学到了套路。

先把曼哈顿距离转成切比雪夫距离,坐标 \((x,y)\) 变换成 \((x+y,x-y)\)(题外话,切比雪夫距离也可以转化成曼哈顿距离,只用把 \((x,y)\) 变成 \((\frac{x+y}{2},\frac{x-y}{2})\) 即可)。

容易发现你每次一定是选隔得最远的两个点之一删掉,因为你这段距离现在删只用算一遍,一定是不劣的。所以搜索,map 记忆化一下即可。

点击查看代码
#include<bits/stdc++.h>
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
map<vector<pii>,int>f;
int dfs(vector<pii>v){
	if(v.empty())return 0;
	if(f.count(v))return f[v];
	int a=0,b=0,c=0,d=0;
	for(int i=0;i<(int)v.size();i++){
		if(v[i].fi<v[a].fi)a=i;
		if(v[i].fi>v[b].fi)b=i;
		if(v[i].se<v[c].se)c=i;
		if(v[i].se>v[d].se)d=i;
	}
	if(v[b].fi-v[a].fi>v[d].se-v[c].se){
		vector<pii>v1=v,v2=v;v1.erase(v1.begin()+b),v2.erase(v2.begin()+a);
		return f[v]=min(dfs(v1),dfs(v2))+(v[b].fi-v[a].fi);
	}
	else{
		vector<pii>v1=v,v2=v;v1.erase(v1.begin()+d),v2.erase(v2.begin()+c);
		return f[v]=min(dfs(v1),dfs(v2))+(v[d].se-v[c].se);		
	}
}
char s[70];vector<pii>v;
signed main(){
	for(int i=1;i<=8;i++){
		scanf("%s",s+1);
		for(int j=1;j<=8;j++){
			if(s[j]=='#')v.push_back(mk(i+j,i-j));
		}
	}
	printf("%d\n",dfs(v));
	return 0;
}

C297 【2023 暑期集训】 0707 NOIP模拟赛改错

不想多说。考的跟【数据删除】一样。

原题场

A 圣诞树

原题链接

首先说一下什么是 prufer 序列。prufer 序列可以将一个带标号 n 个结点的树用 \([1,n]\) 中的 n-2 个整数表示。你也可以把它理解为完全图的生成树与数列之间的双射。prufer 序列是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复 n-2 次后就只剩下两个结点,算法结束。

好,我们暂时只需要这些。我们考虑对于每个点 \(i\),钦定它与其父亲相连的边的的

C315 【2023 暑期集训】0711 NOIP 模拟赛 改错

A 字符串

原题链接。简单,但挂分。怎么搞都可以。

B 交友

原题链接

C 料理

原题链接

D 象棋

还没改。

C333 【2023 暑期集训】0717 NOIP 模拟 改错

A 珠宝

原题链接

直接背包你是会的,但是肯定会超时。根据直觉,我们大概率会选取“性价比”最高的一些物品,所以你可以排序,然后在最前面选一些、在剩下的随机选一些,对这些选出来的物品 dp 即可。

这是能过的,但是这是一个乱搞的做法,我们还是要正经一点的。

发现 \(c_i\) 很小,我们可以把 \(c_i\) 相同的当成一组来看。设 \(f_{i,j}\) 表示对于前 \(i\) 种物品,总体积为 \(j\) 的最大价值。令 \(s_{i,j}\) 表示所有体积为 \(i\) 的物品中价值前 \(j\) 大之和,\(cnt_i\) 表示体积为 \(i\) 的物品有多少个。有转移式 \(f_{i,j}=\max\limits_{k=0}^{cnt_i}\left\{f_{i-1,j-ik}+s_{i,k}\right\}\)。因为这个转移只在模 \(i\) 意义下相同的 \(j\) 有影响,我们把 \(j\) 写成 \(pi+q\) 的形式。容易证明此时价值满足四边形不等式,可以决策单调性优化。然后没了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,V=300;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int f[50005],g[50005],p[50005],c[1000005],v[1000005],t[305];vector<int>s[305],w[305];
void solve(int id,int l,int r,int L,int R){
	if(l>r)return;
	if(l==r){
		g[l]=-1;
		for(int i=L;i<=R&&i<=l;i++){
			g[l]=max(g[l],f[p[i]]+s[id][l-i]);
		}
		return;
	}
	int mid=(l+r)>>1,pos=0;g[mid]=-1;
	for(int i=L;i<=R&&i<=mid;i++){
		if(mid-i>t[id])continue;
		if(f[p[i]]+s[id][mid-i]>g[mid]){
			g[mid]=f[p[i]]+s[id][mid-i];
			pos=i;
		}
	}
	solve(id,l,mid-1,L,pos);
	solve(id,mid+1,r,pos,R);
}
int cmp(int x,int y){
	return x>y;
}
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		c[i]=read(),v[i]=read(); 
		w[c[i]].push_back(v[i]);
	}
	for(int i=1;i<=V;i++){
		if(w[i].empty())continue;
		sort(w[i].begin(),w[i].end(),cmp);t[i]=(int)w[i].size();
		s[i].resize(t[i]+5);s[i][1]=w[i][0];
		for(int j=2;j<=t[i];j++)s[i][j]=s[i][j-1]+w[i][j-1];
	}
	for(int i=1;i<=V;i++){
		if(w[i].empty())continue;
		for(int j=0;j<i;j++){
			int tot=0;
			for(int k=j;k<=m;k+=i)p[++tot]=k;
			solve(i,1,tot,1,tot);
			for(int k=1;k<=tot;k++)f[p[k]]=g[k];
		}
		for(int j=1;j<=m;j++)f[i]=max(f[i-1],f[i]);
	}
	for(int i=1;i<=m;i++)printf("%lld ",f[i]);
	return 0;
}

B 火灾

原题链接。没改错捏。

C 迷宫

原题链接。有点复杂的一道题。

D 小丑

原题链接。有点奇怪的一道题。

C346 【2023 暑期集训】0719 NOIP模拟 改错

部分分很多,好评。

A 镜子

比较典。可以把当前在哪个点,以及是从哪个方向转移过来的记为一个状态。显然多次在一个点放置镜子一定不优,01bfs 即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
const int dx[]={-1,1,0,0},dy[]={0,0,-1,1};
const int d1[]={3,2,1,0},d2[]={2,3,0,1};
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
} 
int n,m,sx,sy,ex,ey,d[1005][1005][5];
struct Node{
	int x,y,o,k;
};
string s[1005];
int check(int x,int y){
	return (x>=0&&x<=n-1&&y>=0&&y<=m-1&&s[x][y]!='X');
}
int bfs(){
	for(int i=0;i<n;i++)for(int j=0;j<m;j++)for(int o=0;o<4;o++)d[i][j][o]=inf;
	deque<Node>q;
	for(int i=0;i<4;i++)q.push_back((Node){sx,sy,i,0}),d[sx][sy][i]=0;
	while(!q.empty()){
		Node u=q.front();q.pop_front();
		int x=u.x,y=u.y,o=u.o,k=u.k,nx,ny,no,nk;
		if(d[x][y][o]<k)continue;
		if(s[x][y]!='/'&&s[x][y]!='\\'){
			nx=x+dx[d1[o]],ny=y+dy[d1[o]],no=d1[o],nk=k+1;
			if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_back((Node){nx,ny,no,nk});
			nx=x+dx[d2[o]],ny=y+dy[d2[o]],no=d2[o],nk=k+1;
			if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_back((Node){nx,ny,no,nk});
			nx=x+dx[o],ny=y+dy[o],no=o,nk=k;
			if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});
		}
		else if(s[x][y]=='/'){
			nx=x+dx[d1[o]],ny=y+dy[d1[o]],no=d1[o],nk=k;
			if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});			
		}
		else{
			nx=x+dx[d2[o]],ny=y+dy[d2[o]],no=d2[o],nk=k;
			if(check(nx,ny)&&d[nx][ny][no]>nk)d[nx][ny][no]=nk,q.push_front((Node){nx,ny,no,nk});				
		}
	}
	int res=inf;
	for(int o=0;o<4;o++)res=min(res,d[ex][ey][o]);
	return res;
}
signed main(){
	n=read(),m=read();
	for(int i=0;i<n;i++)getline(cin,s[i]);
	for(int i=0;i<n;i++)for(int j=0;j<m;j++){
		if(s[i][j]=='A')sx=i,sy=j;
		if(s[i][j]=='B')ex=i,ey=j;
	}
	printf("%lld\n",bfs());
	return 0;
}

B 匹配

原题链接。考虑按大小从小到大考虑:如果一头牛没有被安排进牛棚里,那么所有大小不小于它的牛棚就必须都不为空。

可以把牛和棚放到一起按大小排序(相同体积牛在前面),然后扫过去依次安排。定义 \(f_{i,j,0/1}\) 表示考虑这个混合序列的前 \(i\) 个,其中有 \(j\) 头牛需要分配的牛棚不在前 \(i\) 个里,是否有牛不会被分配牛棚。随便 DP 一下即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
} 
struct Node{
	int op,x;
}a[6005];
int cmp(Node x,Node y){
	if(x.x^y.x)return x.x<y.x;
	return x.op<y.op;
}
int s[3005],t[3005],f[2][3005][2];
signed main(){
	int n=read(),m=0;
	for(int i=1;i<=n;i++)s[i]=read(),a[++m]=(Node){0,s[i]};
	for(int i=1;i<=n;i++)t[i]=read(),a[++m]=(Node){1,t[i]};
	sort(a+1,a+m+1,cmp);f[0][0][0]=1;
	for(int i=1;i<=m;i++){
		int o=i&1ll;
		for(int j=0;j<=n;j++)f[o][j][0]=f[o][j][1]=0;
		if(a[i].op==0){
			for(int j=0;j<=n;j++){
				if(j-1>=0)f[o][j][0]=(f[o][j][0]+f[o^1][j-1][0])%mod;
				if(j-1>=0)f[o][j][1]=(f[o][j][1]+f[o^1][j-1][1])%mod;
				f[o][j][1]=(f[o][j][1]+f[o^1][j][0])%mod;
				f[o][j][1]=(f[o][j][1]+f[o^1][j][1])%mod;
			}
		}
		else{
			for(int j=0;j<=n;j++){
				if(j+1<=n)f[o][j][0]=(f[o][j][0]+f[o^1][j+1][0]*(j+1)%mod)%mod;
				if(j+1<=n)f[o][j][1]=(f[o][j][1]+f[o^1][j+1][1]*(j+1)%mod)%mod;
				f[o][j][0]=(f[o][j][0]+f[o^1][j][0])%mod;
			}
		}
	}
	printf("%lld\n",(f[m&1][0][0]+f[m&1][0][1])%mod);
	return 0;
}

C 修行

原题链接。问题相当于问 \(\sum\limits_{i=1}^{n-1}[p_i>p_{i+1}]=k\) 的排列 \(p\) 的数量。这就等价于问 \(\sum\limits_{i=1}^{n-1}[p_i<p_{i+1}]=n-1-k\) 的排列 \(p\) 的数量,欧拉数单点求一下即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,SIZ=1e5+2;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
	return res;
}
int jc[200005],iv[200005],ij[200005]; 
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int tot,p[100005],np[100005],f[100005];
signed main(){
	int n=read(),m=read();m=n-m;f[1]=1;
	jc[0]=1;for(int i=1;i<=SIZ;i++)jc[i]=jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=SIZ;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=SIZ;i++)ij[i]=ij[i-1]*iv[i]%mod;
	for(int i=2;i<=SIZ;i++){
		if(!np[i])p[++tot]=i,f[i]=qpow(i,n);
		for(int j=1;j<=tot&&i*p[j]<=SIZ;j++){
			np[i*p[j]]=1;f[i*p[j]]=f[i]*f[p[j]]%mod;
			if(i%p[j]==0)break;
		}
	}
	int res=0;
	for(int i=0;i<=m;i++)res=(res+C(n+1,i)*f[m+1-i]%mod*((i&1)?-1:1)+mod)%mod;
	printf("%lld\n",res);
	return 0;
}

D 数数

原题链接。还不太会。

重庆市2023年中学友谊赛Round1

今天考了 cqbz 的联考,感觉非常恐怖啊,被爆杀。还好数据水\yum

不出所料的话接下来几天应该也是考联考,题目都在 cqbz 的 oj 上。

A. 猫捉老鼠

容易发现在基环树上,如果猫抓不到老鼠,当且仅当它们都在基环树的环上,且距离至少为 2。这个时候,老鼠就可以复制猫的动作,游戏就会永远进行下去。可以计算出老鼠走到环上的时间 \(t_1\) 和猫走到老鼠对应的点的时间 \(t_2\)。如果 \(t_1\ge t_2\),说明老鼠会被堵门;否则,就可以按上述方法一直躲下去。

记得特判环长不大于 3 的情况。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[2000005];
int tot,head[1000005],deg[1000005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int cyc[1000005],dis[1000005],bel[1000005];
void dfs(int u,int d,int rt){
	dis[u]=d,bel[u]=rt;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!cyc[v]&&!dis[v])dfs(v,d+1,rt);
	}
}
int p[1000005];
void walk(int u,int id){
	p[u]=id;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(cyc[v]&&!p[v])walk(v,id+1);
	}
}
signed main(){
	freopen("catch.in","r",stdin);
	freopen("catch.out","w",stdout);
	int n=read(),m=read(),siz=0;
	for(int i=1,u,v;i<=n;i++)u=read(),v=read(),add(u,v),add(v,u); 
	queue<int>q;
	for(int i=1;i<=n;i++)if(deg[i]==1)q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();deg[u]=-1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(deg[v]==-1)continue;
			if((--deg[v])==1)q.push(v);
		}
	}
	for(int i=1;i<=n;i++)if(deg[i]!=-1)cyc[i]=1,siz++;
	for(int i=1;i<=n;i++)if(cyc[i])dfs(i,0,i);
	for(int i=1;i<=n;i++){
		if(!cyc[i])continue;
		p[i]=1;
		for(int j=head[i];j;j=e[j].nxt){
			int v=e[j].v;
			if(cyc[v]&&!p[v]){walk(v,2);break;}
		}
		break;
	}
	while(m--){
		int x=read(),y=read();
		if(siz<=3){puts("Yes");continue;}
		int dx=dis[x],dy=dis[y]+min(abs(p[bel[x]]-p[bel[y]]),siz-abs(p[bel[x]]-p[bel[y]]));
		if(dx>=dy){puts("Yes");continue;}
		puts("No");continue;
	}
	return 0;
}

B. 格斗游戏

C. 大脑过载

D. 龙了个龙

很简单,但我没想到()

我们记数组 \(c_{i,j}\) 表示考虑 \(a_1\ldots a_i\),中间 \(j\) 出现了几次。容易发现 \([l,r]\) 合法当且仅当 \(\forall i\in[1,n],c_{l-1,i}\equiv c_{r,i}\pmod k\)。可以对 \(c\) 数组求 hash,于是问题转化为求区间有多少对值相同的数,莫队即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int base1=1303,mod1=100000357;
const int base2=10193,mod2=100000267;
const int T=1000639;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[100005],v1[100005],v2[100005],bel[100005],pw1[100005],pw2[100005],cnt[100005];ll res,ans[100005];
unordered_map<ll,int>mp;
struct Que{
	int l,r,id;
}q[100005];
int cmp(Que x,Que y){
	return bel[x.l]<bel[y.l]||(bel[x.l]==bel[y.l]&&(bel[x.l]&1?x.r<y.r:x.r>y.r));
}
signed main(){
	freopen("dragon.in","r",stdin);
	freopen("dragon.out","w",stdout);
	int n=read(),k=read(),m=read();
	pw1[0]=1;for(int i=1;i<=n;++i)pw1[i]=1ll*pw1[i-1]*base1%mod1;
	pw2[0]=1;for(int i=1;i<=n;++i)pw2[i]=1ll*pw2[i-1]*base2%mod2;
	for(int i=1;i<=n;++i){
		a[i]=read();
		v1[i]=(v1[i-1]-1ll*pw1[a[i]-1]*cnt[a[i]]%mod1+mod1)%mod1;
		v2[i]=(v2[i-1]-1ll*pw2[a[i]-1]*cnt[a[i]]%mod2+mod2)%mod2;
		cnt[a[i]]=(cnt[a[i]]+1)%k;
		v1[i]=(v1[i]+1ll*pw1[a[i]-1]*cnt[a[i]]%mod1)%mod1;
		v2[i]=(v2[i]+1ll*pw2[a[i]-1]*cnt[a[i]]%mod2)%mod2;
	}
	int siz=(int)sqrt(2*n);
	for(int i=1;i<=n;++i)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=m;++i)q[i].l=read()-1,q[i].r=read(),q[i].id=i;
	sort(q+1,q+m+1,cmp);
	for(int i=1,L=1,R=0;i<=m;++i){
		int l=q[i].l,r=q[i].r,id=q[i].id;
		while(L>l)L--,res+=mp[1ll*T*v1[L]+v2[L]],mp[1ll*T*v1[L]+v2[L]]++;
		while(R<r)R++,res+=mp[1ll*T*v1[R]+v2[R]],mp[1ll*T*v1[R]+v2[R]]++;
		while(L<l)mp[1ll*T*v1[L]+v2[L]]--,res-=mp[1ll*T*v1[L]+v2[L]],L++;
		while(R>r)mp[1ll*T*v1[R]+v2[R]]--,res-=mp[1ll*T*v1[R]+v2[R]],R--;
		ans[id]=res;
	}
	for(int i=1;i<=m;++i)printf("%lld\n",ans[i]);
	return 0;
}

C0299 【0828 B组】模拟测试

是 CW 的考试。

唉,不知道自己考试的时候在想些什么。

A 【0828 B组】交换

容易发现答案是所有逆序对的差的和。证明略。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,ans,p[1000005],d[1000005],c1[1000005],c2[1000005];
void add1(int x,int v){
	for(;x<=n;x+=x&-x)c1[x]+=v;
}
void add2(int x,int v){
	for(;x<=n;x+=x&-x)c2[x]+=v;
}
int ask1(int x){
	int res=0;
	for(;x;x-=x&-x)res+=c1[x];
	return res;
}
int ask2(int x){
	int res=0;
	for(;x;x-=x&-x)res+=c2[x];
	return res;
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)p[i]=read(),d[p[i]]=i,add1(i,p[i]),add2(i,1);
	for(int i=1;i<=n;i++)ans+=ask1(d[i])-ask2(d[i])*i,add1(d[i],-i),add2(d[i],-1);
	printf("%lld\n",ans);
	return 0;
}

B 【0828 B组】游戏

唉,唉唉。

\(P_i\) 表示已经操作了 \(i\) 次,仍然没有消去 1 的概率。显然答案等于 \(\sum\limits_{i=0}^{k-1}P_i\)

发现这个没有消去 1 等价于这些条件:\([a_1,a_2)\) 间操作次数不超过 0 次,\([a_1,a_3)\) 间操作次数不超过 1 次 \(\ldots\) 那么令 \(f_{i,j}\) 表示考虑前 \(i\) 个限制,操作了 \(j\) 次,没有消去 1 的概率。有转移式

\[f_{i,j}=\sum\limits_{l=0}^{j}\binom{j}{l}\left(\frac{a_{i+1}-a_i}{n}\right)^lf_{i-1,j-l} \]

且任何时候都有 \(j<i\)。时间复杂度 \(\mathcal{O}(n^3)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=1ll*b*b%mod)if(p&1)res=1ll*res*b%mod;
	return res;
}
int jc[505],iv[505],ij[505]; 
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return 1ll*jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int n,m,ivn,a[505],f[2][505];
signed main(){
	scanf("%d%d",&n,&m),ivn=qpow(n%mod,mod-2);
	jc[0]=1;for(int i=1;i<=m;i++)jc[i]=1ll*jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=m;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=m;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod; 
	f[0][0]=1,a[m]=n+1;for(int i=0;i<m;i++)scanf("%d",&a[i]);
	for(int i=1;i<m;i++)for(int j=0;j<=i;j++){
		int P=1ll*(a[i+1]-a[i])*ivn%mod,now=1;f[i&1][j]=0;
		for(int k=0;k<=j;k++)f[i&1][j]=(f[i&1][j]+1ll*f[(i-1)&1][j-k]*now%mod*C(j,k)%mod)%mod,now=1ll*now*P%mod;
	}
	int ans=0;for(int i=0;i<m;i++)ans=(ans+f[(m-1)&1][i])%mod;
	printf("%d\n",ans);
	return 0;
}

C 【0828 B组】树题

唉,唉唉唉。

先看 \(type=0\)。定义 \(f_{u,0/1/2}\) 表示在 \(u\) 的子树内,节点 \(u\) 没被覆盖/\(u\) 被覆盖且儿子没被覆盖/\(u\) 和一个儿子被覆盖。有转移式

\[f_{u,0}=\prod\limits_{v\in son_u}f_{v,0}+\sum\limits_{x,y\in son_u\land x\not=y}f_{x,1}f_{y,2}\prod\limits_{v\in son_u\land v\not=x\land v\not=y}f_{v,0} \]

\[f_{u,1}=\prod\limits_{v\in son_u}f_{v,0} \]

\[f_{u,2}=\sum\limits_{x\in son_u}f_{x,1}\prod\limits_{v\in son_u\land v\not=x}f_{v,0} \]

但是现在还要求所有方案里的四元组的数量,怎么办?可以把这两个信息压到一个二元组 \((a,b)\) 里。且

\[(a,b)+(c,d)=(a+c,b+d) \]

\[(a,b)\times (c,d)=(a\times c,a\times d+b\times c) \]

然后直接 dp。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
struct Info{
	int x,y;
	Info():x(0),y(0){}
	Info(int _x,int _y):x(_x),y(_y){}
	Info operator +(const Info &b)const{
		return (Info){(x+b.x)%mod,(y+b.y)%mod};
	}
	Info operator *(const Info &b)const{
		return (Info){1ll*x*b.x%mod,(1ll*y*b.x%mod+1ll*b.y*x%mod)%mod};
	}
}f[500005][3];
void dfs(int u,int fa){
	Info g[2][2],h[2][2][2];int now=0;
	g[now][0]=h[now][0][0]=f[u][0]=f[u][1]=Info(1,0);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u),now^=1;
		f[u][0]=f[u][0]*f[v][0],f[u][1]=f[u][1]*f[v][0];
		g[now][0]=g[now^1][0]*f[v][0],g[now][1]=g[now^1][0]*f[v][1]+g[now^1][1]*f[v][0];
		h[now][0][0]=h[now^1][0][0]*f[v][0],h[now][1][0]=h[now^1][0][0]*f[v][1]+h[now^1][1][0]*f[v][0];
		h[now][0][1]=h[now^1][0][0]*f[v][2]+h[now^1][0][1]*f[v][0],h[now][1][1]=h[now^1][0][1]*f[v][1]+h[now^1][1][0]*f[v][2]+h[now^1][1][1]*f[v][0];
	}
	f[u][2]=g[now][1],f[u][0]=f[u][0]+h[now][1][1]*Info(1,1);
}
signed main(){
	int n=read(),type=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs(1,0);
	if(type==0)printf("%d\n",f[1][0].x);
	else printf("%d\n%d\n",f[1][0].x,f[1][0].y);
	return 0;
}

D 【0828 B组】木又寸题

唉,唉唉唉唉。

\(u=v\) 怎么做?我会离线点分治!

\(u\not=v\) 怎么做?我会拆成离 \(u\) 不超过 \(d\) 的 + 离 \(v\) 不超过 \(d\) 的 - 离 \(u,v\) 都不超过 \(d\) 的!

求离 \(u,v\) 都不超过 \(d\) 的怎么做?我会找 \(u\to v\) 的中点 \(m\),求离 \(m\) 不超过 \(d-\frac{\text{dist}(u,v)}{2}\) 的!

\(u\to v\) 中点是条边怎么办?我会对每条边拆点!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[800005];
int tot,head[400005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int pa[400005][22],dep[400005];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1,pa[u][0]=fa;
	for(int i=0;i<=20;i++)pa[u][i+1]=pa[pa[u][i]][i];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u);
	}
}
int getlca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	for(int i=20;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
	return pa[x][0];
}
int jump(int u,int d){
	for(int i=20;i>=0&&d;i--)if((d>>i)&1)u=pa[u][i],d-=(1<<i);
	return u;
}
struct Que{
	int val,d,id;
};
vector<Que>q[400005];
void addmid(int u,int v,int d,int id){
	int lca=getlca(u,v),l=dep[u]+dep[v]-2*dep[lca];
	q[u].push_back((Que){1,d,id}),q[v].push_back((Que){1,d,id});
	if(d-l/2<0)return;
	if(dep[u]-dep[lca]>=l/2)q[jump(u,l/2)].push_back((Que){-1,d-l/2,id});
	else q[jump(v,l/2)].push_back((Que){-1,d-l/2,id});
}
int root,siz[400005],ma[400005],vis[400005];
void dfsroot(int u,int fa,int sum){
	siz[u]=1,ma[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa||vis[v])continue;
		dfsroot(v,u,sum);siz[u]+=siz[v],ma[u]=max(ma[u],siz[v]);
	}
	ma[u]=max(ma[u],sum-siz[u]);
	if(ma[u]<ma[root])root=u;
}
int num,a[400005],dis[400005],bel[400005];vector<int>p[400005];
void dfsdis(int u,int fa){
	a[++num]=u,p[bel[u]].push_back(u);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa||vis[v])continue;
		dis[v]=dis[u]+1,bel[v]=bel[u],dfsdis(v,u);
	}
}
int n,m,ans[200005];
struct BIT{
	int c[400005];
	void add(int x,int y){
		x++;for(;x<=2*n;x+=x&-x)c[x]+=y;
	}
	int ask(int x){
		if(x<0)return 0;
		int res=0;x++;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
}Tr;
void calc(int u){
	num=0,a[++num]=u,dis[u]=0,bel[u]=u;
	vector<int>tmp;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(vis[v])continue;
		dis[v]=1,bel[v]=v,dfsdis(v,u);tmp.push_back(bel[v]);
	}
	for(int i=1;i<=num;i++){
		if(a[i]<=n)Tr.add(dis[a[i]],1);
	}
	for(int i=1;i<=num;i++){
		for(auto x:q[a[i]]){
			int val=x.val,d=x.d,id=x.id;
			ans[id]+=val*Tr.ask(d-dis[a[i]]);
		}
	}
	for(int i=1;i<=num;i++){
		if(a[i]<=n)Tr.add(dis[a[i]],-1);
	}
	for(auto r:tmp){
		for(auto i:p[r])if(i<=n)Tr.add(dis[i],1);
		for(auto i:p[r]){
			for(auto x:q[i]){
				int val=x.val,d=x.d,id=x.id;
				ans[id]-=val*Tr.ask(d-dis[i]);
			}
		}
		for(auto i:p[r])if(i<=n)Tr.add(dis[i],-1);
		p[r].clear();
	}
}
void solve(int u){
	vis[u]=1,calc(u);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(vis[v])continue;
		root=0,dfsroot(v,0,siz[v]),solve(root);
	}
}
signed main(){
	n=read(),m=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,i+n),add(i+n,u),add(v,i+n),add(i+n,v);
	dfs(1,0);
	for(int i=1,u,v,d;i<=m;i++)u=read(),v=read(),d=min(n-1,read())*2,addmid(u,v,d,i);
	ma[0]=inf;root=0;dfsroot(1,0,2*n-1);solve(root);
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

重庆市2023年中学友谊赛Round4

第四场联考!这一场 CWOI 上也有题目

A. 月华

稍微想复杂了点。

因为题目不保证 \(P\) 是质数,直接二维前缀积显然是不行的。考虑分解出 \(P\) 中的质因子,集合记作 \(S\)。对于 \(a_{i,j}\),它的因子有两种情况:如果不在 \(S\) 中,就可以直接前缀积送走;反之,可以枚举 \(S\) 中每一个元素 \(p_i\),预处理前缀和后对每个询问算出区间内有多少个 \(p_i\),直接算对应次幂即可。

时间复杂度大概是 \(\mathcal{O}(\log P+nm+q(\omega(P)+\log P))\)?反正是个非常宽松的上界,可以线性求逆元优化成这样。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define y1 y3456
using namespace std;
const int mod=147744151;
char buf[(1<<15)+5],*p1,*p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<15,stdin),p1==p2)?EOF:*p1++)
int read(){
	int f=0,x=0;char ch=getchar();
	while(!isdigit(ch)){f|=(ch=='-');ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f?-x:x;
}
#define ull unsigned long long
#define ui128 __uint128_t
struct Barrett{
	ull d;ui128 m;
	void init(ull _d){
		d=_d,m=(((ui128)(1)<<64)/d);
	}
	ull operator()(ull a){
		ull w=(m*a)>>64;w=a-w*d;
		if(w>=d)w-=d;return w;
	}
}MOD;
int tot,B[15];
inline void init(int P){
	for(int i=2;1ll*i*i<=P;++i){
		if(P%i==0){
			B[++tot]=i;
			while(P%i==0)P/=i;
		}
	}
	if(P>1)B[++tot]=P;
}
int n,m,P,q,a[1005][1005],iva[1005][1005],A[1000005],s[1005][1005],p[1005][1005],ip[1005][1005];
inline int exgcd(int a,int b,int &x,int &y){
	if(b==0){x=1,y=0;return a;} 
	int g=exgcd(b,a%b,x,y),z=x;x=y;y=z-a/b*y;return g;
}
inline int inv(int a){
	int x,y;exgcd(a,P,x,y);
	return MOD(x%P+P);
}
int Qx1[2000005],Qy1[2000005],Qx2[2000005],Qy2[2000005],res[2000005],t[10005],jc[1000005],ij[1000005],iv[1000005];
signed main(){
	n=read(),m=read(),P=read();init(P);t[0]=1;MOD.init(P);
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)a[i][j]=read();
	q=read();
	for(int i=1;i<=q;++i)Qx1[i]=read(),Qy1[i]=read(),Qx2[i]=read(),Qy2[i]=read(),res[i]=1;
	for(int k=1;k<=tot;++k){
		for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
			int num=0;while(a[i][j]%B[k]==0)a[i][j]/=B[k],num++;
			s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+num;
		}
		for(int i=1;i<=s[n][m];++i)t[i]=MOD((ll)t[i-1]*B[k]);
		for(int i=1;i<=q;++i){
			int x1=Qx1[i],y1=Qy1[i],x2=Qx2[i],y2=Qy2[i];
			res[i]=MOD((ll)res[i]*t[s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]]);
		}
	}
	jc[0]=1;for(int i=1;i<=n*m;++i)jc[i]=MOD((ll)jc[i-1]*a[(i-1)/m+1][(i-1)%m+1]);
	ij[n*m]=inv(jc[n*m]);for(int i=n*m-1;i>=1;--i)ij[i]=MOD((ll)ij[i+1]*a[i/m+1][i%m+1]);
	for(int i=1;i<=n*m;++i)iva[(i-1)/m+1][(i-1)%m+1]=MOD((ll)jc[i-1]*ij[i]);
	for(int i=0;i<=n;++i)p[i][0]=ip[i][0]=1;
	for(int j=0;j<=m;++j)p[0][j]=ip[0][j]=1;
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
		p[i][j]=MOD(MOD(MOD((ll)p[i-1][j]*p[i][j-1])*ip[i-1][j-1])*a[i][j]);
		ip[i][j]=MOD(MOD(MOD((ll)ip[i-1][j]*ip[i][j-1])*p[i-1][j-1])*iva[i][j]);
	}
	for(int i=1;i<=q;++i){
		int x1=Qx1[i],y1=Qy1[i],x2=Qx2[i],y2=Qy2[i];
		res[i]=MOD((ll)res[i]*(MOD(MOD(MOD((ll)p[x2][y2]*ip[x1-1][y2])*ip[x2][y1-1])*p[x1-1][y1-1])));
	}
	int ans=0;
	for(int i=1;i<=q;++i)ans=(ans+(i^res[i]))%mod;
	printf("%d",ans);
	return 0;
}

C. 夏虫

没在线段树上修改 \(a_i\) 的值调半天,难绷。

如果没有修改操作,我们可以定义 \(f_i\) 表示开始位置为 \(i\) 的答案,然后找两边第一个比它大的转移,记忆化搜索,这是 \(\mathcal{O}(n\log n)\) 的。

加上修改操作后,原先的方法并不好维护。可以发现令 \(L_i,R_i\) 表示 \(a_1\ldots a_i\) 的后缀最大值集合与 \(a_i\ldots a_n\) 的前缀最大值集合,\(f_i=|L_i\cup R_i|-1\),思考这个交换操作会让哪些 \(L,R\) 发生变化。

假设 \(a_x<a_{x+1}\)。令这两个数左右两边第一个不小于 \(a_x\) 的位置为 \(i,j\),那么 \(L_{i+1}\ldots L_{x-1}\)\(R_{x+2}\ldots R_{j-1}\) 可能会发生变化(我们单独考虑 \(x,x+1\) 的变化)。更具体的,以 \(L\) 为例,如果这些 \(L\) 里有 \(a_x\),那么答案并不会变,否则会减一。怎么判断?\(a_i\) 是第一个不小于 \(a_x\) 的位置,如果 \(a_p=a_x\) 存在那么一定有 \(a_i=a_x\)。其他情况类似。

对于 \(x,x+1\),我们可以在求出其它点的答案后沿用最开始的方法。时间复杂度 \(\mathcal{O}((n+q)\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,q,a[100005];
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int f,tag,ma;
	}c[400005];
	void pushup(int p){
		c[p].f=c[ls].f+c[rs].f;
		c[p].ma=max(c[ls].ma,c[rs].ma);  
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
		c[ls].f+=ln*c[p].tag,c[rs].f+=rn*c[p].tag;
		c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
		c[p].tag=0;  
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){c[p].ma=a[l];c[p].f=-1;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){c[p].f=k;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	void change(int l,int r,int p,int x,int k){
		if(l==r){c[p].ma=k;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(x<=mid)change(lson,x,k);
		else change(rson,x,k);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int k){
		if(L>R)return;
		if(L<=l&&r<=R){c[p].f+=(r-l+1)*k,c[p].tag+=k;return;}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(lson,L,R,k);
		if(R>mid)add(rson,L,R,k);
		pushup(p);
	}
	int ask(int l,int r,int p,int L,int R){
		if(L>R)return 0;
		if(L<=l&&r<=R)return c[p].f;
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res+=ask(lson,L,R);
		if(R>mid)res+=ask(rson,L,R);
		return res;
	}
	int query(int l,int r,int p,int L,int R){
		if(L>R)return -inf;
		if(L<=l&&r<=R)return c[p].ma;
		int mid=(l+r)>>1,res=-inf;pushdown(l,r,p);
		if(L<=mid)res=max(res,query(lson,L,R));
		if(R>mid)res=max(res,query(rson,L,R));
		return res;
	}
	int Findl(int l,int r,int p,int x,int k){
		if(x<1)return 0;
		if(l==r){return (c[p].ma>=k)?l:0;}
		int mid=(l+r)>>1,res=0;
		if(x>mid){
			if(c[rs].ma>=k)res=Findl(rson,x,k);
			if(res==0&&c[ls].ma>=k)res=Findl(lson,x,k);
		}
		else if(c[ls].ma>=k)res=Findl(lson,x,k);
		return res;
	}
	int Findr(int l,int r,int p,int x,int k){
		if(x>n)return n+1;
		if(l==r){return (c[p].ma>=k)?l:n+1;}
		int mid=(l+r)>>1,res=n+1;
		if(x<=mid){
			if(c[ls].ma>=k)res=Findr(lson,x,k);
			if(res==n+1&&c[rs].ma>=k)res=Findr(rson,x,k);
		}
		else if(c[rs].ma>=k)res=Findr(rson,x,k);
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int dfs(int x){
	if(Tr.ask(1,n,1,x,x)>=0)return Tr.ask(1,n,1,x,x);
	int L=Tr.Findl(1,n,1,x-1,a[x]+1),R=Tr.Findr(1,n,1,x+1,a[x]+1);
	if(L==0&&R==n+1)return Tr.update(1,n,1,x,0),0;
	else if(L==0)return Tr.update(1,n,1,x,dfs(R)+1),dfs(R)+1;
	else if(R==n+1)return Tr.update(1,n,1,x,dfs(L)+1),dfs(L)+1;
	else if(a[L]!=a[R])return Tr.update(1,n,1,x,((a[L]>a[R])?dfs(R):dfs(L))+1),((a[L]>a[R])?dfs(R):dfs(L))+1;
	else return Tr.update(1,n,1,x,max(dfs(L),dfs(R))+1),max(dfs(L),dfs(R))+1;
}
signed main(){
	n=read(),m=read();a[0]=a[n+1]=inf;
	for(int i=1;i<=n;i++)a[i]=read();
	Tr.build(1,n,1);
	for(int i=1;i<=n;i++)dfs(i);
	q=read();
	while(q--){
		int x=read(),l=read(),r=read(),p;
		Tr.change(1,n,1,x,a[x+1]);Tr.change(1,n,1,x+1,a[x]);
		if(a[x]<a[x+1]){
			if(x>1){
				p=Tr.Findl(1,n,1,x-1,a[x]);
				if(a[p]!=a[x])Tr.add(1,n,1,p+1,x-1,-1);
			}
			if(x+1<n){
				p=Tr.Findr(1,n,1,x+2,a[x]);
				if(a[p]!=a[x])Tr.add(1,n,1,x+2,p-1,1);				
			}
		}
		else if(a[x]>a[x+1]){
			if(x>1){
				p=Tr.Findl(1,n,1,x-1,a[x+1]);
				if(a[p]!=a[x+1])Tr.add(1,n,1,p+1,x-1,1);				
			}
			if(x+1<n){
				p=Tr.Findr(1,n,1,x+2,a[x+1]);
				if(a[p]!=a[x+1])Tr.add(1,n,1,x+2,p-1,-1);				
			}
		}
		swap(a[x],a[x+1]);Tr.update(1,n,1,x,-1);Tr.update(1,n,1,x+1,-1);
		Tr.update(1,n,1,x,dfs(x));Tr.update(1,n,1,x+1,dfs(x+1));
		printf("%lld\n",Tr.ask(1,n,1,l,r)*m+(r-l+1)*(n-1));
	}
	return 0;
}

D. 万分之一的光

比较精巧的一道题?

如果按照题意去考虑删点的顺序,会发现完全无法记录状态,考虑转化。可以把原问题看做给树上每条边定向,按拓扑序删点。这样可以 \(\mathcal{O}(2^nn)\)。更进一步的,此时的价值也可以继续拆分。如果 \(i\) 可以走到 \(s_i\) 个点,答案就是 \(\sum\limits_{i=1}^na_is_i\)

定义 \(f_{i,j}\) 表示 \(i\) 在自己的子树内可以到达 \(j\) 个点,且父边定向为 \(fa_i\to i\) 的最大价值;\(g_{i,j}\) 表示 \(i\) 在自己的子树外可以到达 \(j\) 个点,且父边定向为 \(i\to fa_i\) 的最大价值。对于从儿子 \(v\) 转移到当前节点 \(u\),再定义 \(h_{i,q,j,k}\) 表示强制钦定 \(i\) 最终能到达 \(q\) 个点,目前只考虑了前 \(j\) 个儿子,且当前能到达子树内 \(k\) 个点的最大价值。有

\[\begin{aligned} h_{u,q,0,1}&=q\times a_u\\ h_{u,q,j+1,k}&=\max\left\{h_{u,q,j,k}+g_{v,q}\right\}\\ h_{u,q,j+1,k+t}&=\max\left\{h_{u,q,j,k}+f_{v,t}\right\}\\ \end{aligned} \]

\(h\) 可以只保留两维,如果最外层枚举 \(q\) 可以只保留一维。同时,从 \(h\) 转移回 \(f,g\) 也有

\[\begin{aligned} f_{u,q}&=h_{q,q}\\ g_{u,q-j}&=\max\left\{h_{q,j}\right\}\\ \end{aligned} \]

时间复杂度 \(\mathcal{O}(n^3)\),空间复杂度 \(\mathcal{O}(n^2)\)。时间复杂度是对的是因为里面一层等价于树形背包。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[805];
int tot,head[405];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,a[405],siz[405],f[405][405],g[405][405],h[405],t[405];
void dfs(int u,int fa){
	for(int i=1;i<=n;i++)f[u][i]=g[u][i]=-inf;
	siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u);siz[u]+=siz[v];
	}
	for(int q=1;q<=n;q++){
		for(int j=1;j<=n;j++)h[j]=t[j]=-inf;
		h[1]=q*a[u];int now=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;if(v==fa)continue;
			for(int j=1;j<=now;j++)t[j]=max(t[j],h[j]+g[v][q]);
			for(int j=1;j<=now;j++)for(int k=1;k<=siz[v]&&j+k<=n;k++)t[j+k]=max(t[j+k],h[j]+f[v][k]);
			now=min(now+siz[v],q);
			for(int j=1;j<=now;j++)h[j]=t[j],t[j]=-inf;
		}
		f[u][q]=h[q];
		for(int j=1;j<=q;j++)g[u][q-j]=max(g[u][q-j],h[j]);
	} 
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs(1,0);int ans=-inf;
	for(int i=1;i<=n;i++)ans=max(ans,f[1][i]);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2023-07-05 10:14  xx019  阅读(8)  评论(0)    收藏  举报