牛客2025多校 R5

牛客2025多校 R5

I:
题目大意:

image-20250731190048954

void solve(){
    LL n;
    cin>>n;
    LL s=(n+1)*n/2;
    LL ans=9e18;
    for (LL i=1;i*i<=s;i++)
        if (s%i==0&&(i>=n||s/i>=n))
            ans=min(ans,2LL*(i+s/i));
    cout<<ans;
}

可以计算出总面积为 \(n(n+1)/2\) ,题目的约束下一点存在合适的矩形可以被构造出来,那么矩形的长宽为 \(a,b\)

枚举合适的 \(a\cdot b=S=n(n+1)/2\) 得到答案,时间复杂度为 \(O(n)\) ,特别的 \(a,b\) 必须满足的条件是都要大于等于 \(n\) ,即不能比最长的矩形还要小

E:

题目大意:

image-20250731190438997

int a[100010];
int d[100010][32];
int pre[100010][32];
int x[32][4];

void solve(){
	int n;
	cin>>n;
	for (int i=1;i<=n;i++) cin>>a[i];
	for (int i=1;i<=n;i++){
		int x=a[i],idx=0;
		while (x){
			d[i][++idx]=(x&1);
			x>>=1;
		}
	}
	for (int i=1;i<=n;i++)
		for (int j=1;j<=30;j++)
			pre[i][j]=pre[i][j-1]^d[i][j];
	LL ans=0;
	for (int i=1;i<=30;i++){
		for (int j=1;j<=n;j++){
			if (pre[j][i]==0&&d[j][i]==0) x[i][0]++;
			if (pre[j][i]==0&&d[j][i]==1) x[i][1]++;
			if (pre[j][i]==1&&d[j][i]==0) x[i][2]++;
			if (pre[j][i]==1&&d[j][i]==1) x[i][3]++;
		}
		ans+=1ll*(x[i][0]*x[i][3]+x[i][2]*x[i][1])*(1<<(i-1));
	}
	cout<<ans;
}

考虑每一位的贡献问题,当 \(a\oplus_m b\) 的结果在某一位上为 \(1\) 时,\(a,b\) 在这一位上会被约束

明显的有如果异或后这一位为 \(1\) ,那么 \(a,b\) 在这一位上一定不会相同,现在考虑这一位之前异或结果为 \(1\) 的数量

如果 \(a\) 的这一位为 \(1\) 且这是第奇数个 \(1\) ,那么只有当 \(b\) 的这一位为 \(0\) 且已经有了奇数个 \(1\) 才能产生贡献

简单证明:

\(a\) 的这一位是第 \(x\)\(1\) ,那么之前就有 \(x-1\)\(1\)\(b\) 的这一位之前\(y\)\(1\),共有 \(m\)

那么普通异或的结果是这一位之前有 \(\mathrm{min}(m-(x-1),y)+\mathrm{min}(m-y,(x-1))\)\(1\) ,可以发现如果取 \(\mathrm{min}(m-(x-1),y)=y\) 时必有 \(\mathrm{min}(m-y,(x-1))=(x-1)\) ,所以普通异或后的结果在这一位之前有 \(2m-x-y+1\)\(x+y-1\)\(1\)

当且仅当 \(x,y\) 的奇偶性不同时才能保证这一位异或后的 \(1\) 是第奇数个

ans+=1ll*(x[i][0]*x[i][3]+x[i][2]*x[i][1])*(1<<(i-1));

最后单独计算每一位的贡献求和后得到答案

J:
题目大意:

image-20250731194221510

int n,m;
vector<vector<int>> g;
vector<vector<int>> dis;
vector<vector<bool>> vis;
vector<pair<int,int>> zro;
int dx[]={0,0,-1,1};
int dy[]={1,-1,0,0};

bool judge(int T){
    int umin=-Iinf,umax=Iinf,vmin=-Iinf,vmax=Iinf;
    bool f=0;
    for(auto [x,y]:zro){
        if(dis[x][y]>T){
            f=1;
            int u=x+y,v=x-y;
            umin=max(umin,u-T);
            umax=min(umax,u+T);
            vmin=max(vmin,v-T);
            vmax=min(vmax,v+T);
        }
    }
    if(!f)return 1;
    if(umin>umax||vmin>vmax)return 0;
    for(auto [x,y]:zro){
        int u=x+y,v=x-y;
        if(u>=umin&&u<=umax&&v>=vmin&&v<=vmax)return 1;
    }
    return 0;
}

void solve(){
	cin>>n>>m;
	g.assign(n+1,vector<int>(m+1,0));
	dis.assign(n+1,vector<int>(m+1,1e9));
	vis.assign(n+1,vector<bool>(m+1,0));
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			cin>>g[i][j];
	queue<pair<int,int>> q;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			if (g[i][j]==1){
				dis[i][j]=0;
				q.push({i,j});
				vis[i][j]=1;
			}
			else
				zro.push_back({i,j});
		}
	}

	while (q.size()){
		auto t=q.front();
		q.pop();
		for (int i=0;i<4;i++){
			int tx=t.first+dx[i],ty=t.second+dy[i];
			if (tx<1||ty<1||tx>n||ty>m) continue;
			if (vis[tx][ty]==1) continue;
			vis[tx][ty]=1;
			q.push({tx,ty});
			dis[tx][ty]=dis[t.first][t.second]+1;
		}
	}
		
	int l=-1,r=2e5+1;
	while (l+1!=r){
		int mid=l+r>>1;
		if (judge(mid)) r=mid;
		else l=mid;
	}
	cout<<r<<endl;
}

首先对每一个白色点都预处理出它被染成黑色的时间 \(dis_{i,j}\) ,然后二分时间找是否能满足所有点都被变为黑色

当在时间 \(T\) 内所有点都能被染成黑色,说明所有的 \(dis_{i,j}>T\) 的点都要通过我们手动染色的点被染黑

取所有 \(dis_{i,j}>T\) 的点根据 \(T\) 时间形成的区域(区域内所有点到 \((i,j)\) 这个点的距离都小于 \(T\) ),我们就说如果点 \((i,j)\) 能被染成黑色,当且仅当这个区域内存在我们手动染色的点

对所有区域取交集后,在交集内的白色的点即为可以将所有 \(dis_{i,j}>T\) 的点都染上黑色的点,如果不存在这样的点说明 \(T\) 取的比较小

如果能够取到这样的点,那么就缩小 \(T\) 直到一个确定的值

这样的区域是一个曼哈顿圆,即 \(\lvert u-x\rvert+\lvert v-y\rvert\le T\) ,更改参考系为 \((x-y),(x+y)\)

image-20250731201733599

维护曼哈顿圆在映射坐标系下的交集即可

for(auto [x,y]:zro){
    if(dis[x][y]>T){
    	f=1;
    	int u=x+y,v=x-y;
    	umin=max(umin,u-T);
    	umax=min(umax,u+T);
   	 	vmin=max(vmin,v-T);
    	vmax=min(vmax,v+T);
    }
}

K:
题目大意:

image-20250813142233740

const int mod=998244353;	

LL fac[110];
vector<int> e[200010];
vector<pair<int,int>> path;
int p[200010],x[30];
int fa[200010][20],pre[200010],dep[200010],idx;
LL A[1<<23],B[1<<23],ans;
int n,m,k;

LL ksm(LL a,LL b,LL p){
	LL res=1;
	while (b){
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

void dfs(int x,int p){
	pre[x]=p;
	dep[x]=dep[p]+1;
	fa[x][0]=p;
	for (auto v:e[x]){
		if (v==p) continue;
		dfs(v,x);
	}
}

int lca(int a,int b){
	if (dep[a]<dep[b]) swap(a,b);
	for (int i=18;i>=0;i--){
		if (dep[fa[a][i]]>=dep[b])
			a=fa[a][i];
	}
	if (a==b) return b;
	for (int i=18;i>=0;i--){
		if (fa[a][i]!=fa[b][i])
			a=fa[a][i],b=fa[b][i];
	}
	return fa[a][0];
}

void FWT(LL a[],int op){
	for (int d=2;d<=(1<<m);d<<=1){
		int k=d>>1;
		for (int i=0;i<(1<<m);i+=d){
			for (int j=0;j<k;j++){
				(a[i+j+k]+=a[i+j]*op+mod)%=mod;
			}
		}
	}
}

bool judge(int mid){
	for (int i=0;i<(1<<m);i++) 
		B[i]=ksm(A[i],mid,mod);
	LL sum=0;
	for (int i=0;i<(1<<m);i++){
		if (__builtin_popcount((1<<m)-i-1)&1) sum-=B[i];
		else sum+=B[i];
		sum=(sum+mod)%mod;
	}
	if (sum!=0) ans=sum;
	return sum==0;
}

void solve(){
	
	fac[0]=1;
	for (int i=1;i<=100;i++) fac[i]=fac[i-1]*i%mod;
	
	cin>>n>>m>>k;
	path.resize(n+1);
	for (int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
		path[i]={u,v};
	}
	dfs(1,0);
	for (int i=1;i<=m;i++) cin>>x[i];
	for (int i=1;i<n;i++) 
		if (pre[path[i].first]==path[i].second) 
			swap(path[i].second,path[i].first);
	for (int i=1;i<=18;i++)
		for (int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
	int road=0;
	while (k--){
		int a,b;
		cin>>a>>b;
		int c=lca(a,b);
		int mask=0;
		for (int i=1;i<=m;i++){
			if (lca(path[x[i]].first,c)==c){
				if (lca(path[x[i]].second,a)==path[x[i]].second||
					lca(path[x[i]].second,b)==path[x[i]].second)
				mask|=1<<(i-1);
			}
		}
		road|=mask;
		A[mask]++;
	}
	if (road!=(1<<m)-1){
		cout<<-1<<endl;
		return;
	}
	if (A[(1<<m)-1]){
		cout<<1<<' '<<A[(1<<m)-1]<<endl;
		return;
	}
	FWT(A,1);
	int l=0,r=m+2;
	while (l+1!=r){
		int mid=l+r>>1;
		if (judge(mid)) l=mid;
		else r=mid;
	}
	cout<<r<<' '<<ans*ksm(fac[r],mod-2,mod)%mod;
}

给出的道路是无序的,因为我们在一棵树上寻找答案,所以首先需要对树边进行处理,即 \(path_i=(u_i,v_i)\) 表示 \(i\) 时一条从 \(u_i\)\(v_i\) 的树边,且 \(u_i\) 是上层的节点

然后对每对给出的旅游线路都处理出这条线路上被经过的特定道路,具体流程是:

给定的旅游线路起点终点分别为 \((a,b)\) ,LCA 处理出他们的最近祖先,枚举所有的特定道路

当且仅当存在一条特定道路它的 \(u_i\) 的层数小于 LCA 的层数,且 \(v_i\)\((a,b)\) 任意一个的祖先,\((u_i,v_i)\) 就被 \((a,b)\) 经过

然后是集合计数问题,我们需要找到能够覆盖完所有特定道路的旅游线路集合

类似于集合并集的关系,考虑构造多项式 \(A=a_0+a_1x+\cdots+a_nx^n\) ,其中 \(a_i\) 表示能够通过二进制表示下的 \(i\) 的了旅游线路的条数

例如:\(a_4=4\) 表示能够通过 \(i=100_2\) 即第 \(3\) 条特定道路的旅游线路数

利用 FWT 处理多项式 \(A\) ,考虑二分答案最少旅游线路数

bool judge(int mid){
	for (int i=0;i<(1<<m);i++) 
		B[i]=ksm(A[i],mid,mod);
	LL sum=0;
	for (int i=0;i<(1<<m);i++){
		if (__builtin_popcount((1<<m)-i-1)&1) sum-=B[i];
		else sum+=B[i];
		sum=(sum+mod)%mod;
	}
	if (sum!=0) ans=sum;
	return sum==0;
}

多项式 \(B=A^{mid}\)\(mid\) 条旅游线路能够覆盖的特定道路的集合,\(b_i\) 表示在 \(mid\) 条旅游线路的覆盖下,经过二进制表示下 \(i\) 的方案数

例如:\(b_5=7\) 表示能够通过 \(i=101_2\) 即能够覆盖第 \(1,3\) 条特定道路的方案数

设最后二分出来的最少旅游条数为 \(x\) ,方案数即为 \(b_{2^m-1}/x!\) ,除掉阶乘的原因是在 FWT 中同一个划分会被计算 \(x\) 次,由于我们的方案不能重复,所以需要去掉重复的划分的数量 \(x!\)

又因为每轮二分的 FWT 都会调用一次逆变换,时间复杂度最坏为 \(O(m^22^m)\) ,事实上我们不需要通过逆变换计算出多项式 \(B\) 的每一项的系数,采用容斥原理可以在 \(2^m\) 的时间复杂度内求得任意一项的系数

所以总时间复杂度可以优化为 \(O(m2^m)\)

H:
题目大意:

image-20250814220332401

image-20250814220340592

image-20250814220348313

int n,m,s,t;
int a[110],q[110],b[110],c[110],pre[110];
int dp[110][110][110];

bool judge(int p){
	memset(dp,-0x3f,sizeof dp);
	dp[0][0][0]=0;
	for (int i=0;i<=n;i++){
		for (int j=0;j<=t;j++){
			for (int k=0;k<=j;k++){
				if (dp[i][j][k]>=s) return 1;
				int sum=pre[i];
				if (j<t)
					dp[i][j+1][k+1]=max(dp[i][j+1][k+1],dp[i][j][k]+sum);
				if (i+1<=n){
					int d=(a[i+1]-1)/sum+1;
					if (j+d<=t)
						dp[i+1][j+d][k+d]=max(dp[i+1][j+d][k+d],dp[i][j][k]);
					d=(a[i+1]-c[i+1]-1)/sum+1;
					if (p==0) continue;
					int u=(b[i+1]-1)/p+1;
					if (j+d<=t&&k-u+d>=0)
						dp[i+1][j+d][k-u+d]=max(dp[i+1][j+d][k-u+d],dp[i][j][k]);
				}
			}
		}
	}
	return 0;
}

void solve(){
	cin>>m>>s>>t>>n;
	for (int i=1;i<=n;i++) cin>>a[i]>>q[i]>>b[i]>>c[i];
	pre[0]=m;
	for (int i=1;i<=n;i++) pre[i]=pre[i-1]+q[i];
	int l=-1,r=10001;
	while (l+1!=r){
		int mid=l+r>>1;
		if (judge(mid)) r=mid;
		else l=mid;
	}
	if (r==10001) cout<<-1;
	else cout<<r;
}

二分最小的生产力 \(p\) ,对于每一个 \(p\) 我们都进行一次动态规划判断是否存在这样的一个最优方案使得在 \(t\) 天内可以将科技点胜利槽叠加到 \(s\)

定义 \(dp_{i,j,k}\) 表示解锁了前 \(i\) 个科技,当前为第 \(j\) 天,剩余 \(k\) 天的生产力没有分配时可以得到的最大科技胜利点

对于任意一天 \(j\) ,我们有以下的转移方向:\(sum_i\) 表示解锁第 \(i\) 个科技后每回合可以得到的科技点

  • 不解锁下一项科技,那么有 \(dp_{i,j,k}+sum_i\to dp_{i,j+1,k+1}\)

  • 解锁下一项科技不使用尤里卡,\(dp_{i,j,k}\to dp_{i+1,j+d,k+d}\)\(d\) 表示解锁下一项科技不使用尤里卡时所需要投入的科技点的天数,因为在这一段时间内,我们都不分配生产力,所以待分配的生产力的天数多增加 \(d\)

  • 解锁下一项科技使用尤里卡,\(dp_{i,j,k}\to dp_{i+1,j+d,k+d-u}\)\(u\) 表示解锁下一项科技所需要投入的尤里卡所需的天数,\(d\) 表示解锁下一项科技使用尤里卡时所需要投入的科技点的天数,这一段时间内,我们会度过 \(d\) 天,会消耗 \(u\) 天来分配生产力,所以待分配生产力的天数为 \(k+d-u\)

A:

题目大意:

image-20250815171221668

void solve(){
	int n,k,s,t;
	cin>>n>>k>>s>>t;
	
	if (s==t){
		cout<<0<<endl;
		return;
	}
	
	if (n==k){
		if (s+t==n) cout<<1<<endl;
		else cout<<-1<<endl;
		return;
	}
	
	int L=2*min(n-k,k);
	int sl=s+k-2*min(s,k);
	int sr=s+k-2*max(0,k-n+s);
	int ans=2e9;
	if (s%2==t%2) ans=min(ans,2*((abs(t-s)-1)/L+1));
	if (sl%2==t%2){
		if (t<sl) ans=min(ans,1+2*((sl-t-1)/L+1));
		if (t>sr) ans=min(ans,1+2*((t-sr-1)/L+1));
		if (t>=sl&&t<=sr) ans=min(ans,1);
	}
	if (ans==2e9) cout<<-1<<endl;
	else cout<<ans<<endl;
}

考虑连续两次操作带来的影响:如果第一次改变 \(k\) 枚,第二次也改变 \(k\) 枚,设在这两次中都改变的硬币个数为 \(l\)

那么总的改变硬币个数为 \(2k-2l\) ,必然是偶数个,所以可以递推地得到:

  • 连续两次操作可以改变 \(0\) 枚硬币
  • 连续两次操作可以改变 \(2\) 枚硬币,当 \(k\in [1,n-1]\)
  • …………

可以得到连续两次操作可以改变 \(0,2,\cdots ,2\times\mathrm{min}(n-k,k)\) 枚硬币,证明:

设两次操作后只改变一次状态的硬币个数为 \(2a\) ,则有 \(k-a\) 枚硬币在两次操作后的状态不变

\(2a+k-a\le n\) 这是一定满足的必要条件(至少有 \(2a+k-a\) 枚不同的硬币才能选到)

得到 \(a\le n-k\) 又因为 \(a\in [0,k]\) ,所以 \(a\) 的取值范围为 \([0,\mathrm{min}(n-k,k)]\)

所以可以通过 \(2m,m\in N^+\) 次操作改变至多 \(m\times2\times\mathrm{min}(n-k,k)\) 枚硬币,那么当且仅当 \(s,t\) 的奇偶性相同时,我们才能通过这类操作使得硬币序列满足题意

int L=2*min(n-k,k);
if (s%2==t%2) ans=min(ans,2*((abs(t-s)-1)/L+1));

考虑奇数次操作可以带来的影响,可以看作先做一次单独的操作再做上述的偶数次操作,所以计算这一次单独操作的贡献

设这一次操作将朝上的硬币数量减少 \(i\),那么最终会有 \(s-i+(k-i)=s+k-2i\) 枚硬币朝上

满足的约束有:\(i\in[0,\mathrm{min}(s,k)],k-i\in[0,\mathrm{min}(k,n-s)]\) ,所以 \(i\in[\mathrm{max}(0,k-n+s),\mathrm{min}(s,k)]\)

做一次单独的操作可以将朝上的硬币个数变为 \([s+k-2\times\mathrm{max}(0,k-n+s),s+k-2\times\mathrm{min}(s,k)]\)

分类讨论 \(t\) 在区间上的取值:\(s_l=\mathrm{max}[0,s+k-2\times\mathrm{max}(0,k-n+s)],s_r=\mathrm{min}[n,s+k-2\times\mathrm{min}(s,k)]\)

  • \(t\in [s_l,s_r]\) ,显然只需要一次操作就能满足题意
  • \(t\in[0,s_l]\) ,通过一次操作将朝上的硬币个数变为 \(s_l\) ,然后做上述的偶数次操作
  • \(t\in [s_r,n]\) ,同理

同样的需要在 \(s_l,s_r\)\(t\) 的奇偶性相同时才能做之前的偶数次操作

if (sl%2==t%2){	
	if (t<sl) ans=min(ans,1+2*((sl-t-1)/L+1));
	if (t>sr) ans=min(ans,1+2*((t-sr-1)/L+1));
	if (t>=sl&&t<=sr) ans=min(ans,1);
}
posted @ 2025-08-15 20:09  才瓯  阅读(26)  评论(0)    收藏  举报