暑假集训CSP提高模拟22-23

1.法阵

转化题意,就是每一行有一段连续的东西,每一列有一段连续的空格,求方案数。手模一下,整个图大概是长这个样子

image

由于它是一个向内凹进去的形状,所以它一定有一个转折点,我们可以去枚举这个转折点的位置求方案数,例如 \(A\)

其左边区域的方案数我们以水平线分上下两部分求,因为是方格,所以我们可以观察到一种情况对应一个边界线的形状,

从这里看,实际上从左上角到 \(A\) 的部分的方案数就是从左上角走到 \(A\) 点的不同路径数,对四部分都求一下即可

考虑边界,当 \(A\)\(B\) 重回时的情况在 \(A\) 区域算了一次,在 \(B\) 又算了一次,所以要减去,由于方案数与 \(n,m\) 无关

所以将 \(n,m\) 互换方案数一样,直接乘二

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int mod=998244353;
const int maxn=1<<12;
using namespace std;
int n,m,jc[maxn+5],jcny[maxn+5],ans=0,sum;

void pre(){
	for(int i=(jc[0]=jcny[0]=jcny[1]=1)+1;i<=maxn;i++) 
		jcny[i]=1ll*jcny[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<=maxn;i++) 
		jc[i]=1ll*jc[i-1]*i%mod,jcny[i]=1ll*jcny[i-1]*jcny[i]%mod;
}

int f(int x,int y)
{
//	cout<<jcny[x]<<" "<<jcny[y]<<endl;
	return 1ll*jc[x+y]*jcny[x]%mod*jcny[y]%mod;
}

signed main()
{
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	pre(); 
	for(int i=1;i<=m-1;i++)
	{
		sum=0;
		for(int j=1;j<=n-1;j++)
		{
			sum=(sum+1ll*f(i,j-1)%mod*f(i-1,n-j))%mod;
//			 cout<<c(i,j-1)*c(i-1,n-j)<<endl;
			ans=(ans+1ll*sum*f(m-1-i,j)%mod*f(m-i,n-1-j)%mod)%mod;
		}
	}
//	swap(n,m);
	n^=m^=n^=m;
	for(int i=1;i<=m-1;i++)
	{
		sum=0;
		for(int j=1;j<=n-1;j++)
		{
			ans=(ans+1ll*sum*f(m-1-i,j)%mod*f(m-i,n-1-j)%mod)%mod;
			sum=(sum+1ll*f(i,j-1)%mod*f(i-1,n-j))%mod;
		}
	}
	cout<<(ans<<1)%mod;
	
	return 0;
}

2. 连通块

一个常见套路,把删边改为倒着加边,让后再维护最长直径,可以发现询问一个点时,离他最远的点一定是直径的两个端点之一

可以自己画个图手模一下,反证法很好证。求的时候就可以提前用 \(dfs\) 序求一下每个点的深度和 \(lca\) ,直接求就行。

现在题目就转化成了如果在加边时维护直径,对于两个图,他俩之间连了一条边,那么新直径的端点也一定是在这两个图的两个

直径的四个点里面去取,用上面自己证的那个结论就能证。一共6种情况,并查集维护一下连通性,再存一下端点就行

点击查看代码
#include<bits/stdc++.h>
const int maxn=2e5+10;
using namespace std;
int n,m,head[maxn],to[maxn<<1],nxt[maxn<<1],tot,pu[maxn],vis[maxn];
int fa[maxn],x[maxn],y[maxn],w[maxn],la[maxn],lb[maxn];

inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}

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

struct LCA
{
    int dfn[maxn],tot,dep[maxn],st[20][maxn];
    int get(int x,int y) {return dep[x]<dep[y]?x:y;}
    void dfs(int u,int fa)
    {
        dfn[u]=++tot,st[0][tot]=fa; dep[u]=dep[fa]+1;
        for(int i=head[u];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],u);
    }
    void init()
    {
        dfs(1,0);
        for(int i=1;(1<<i)<=n;i++)
            for(int j=1;j<=n-(1<<i)+1;j++)
                st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
    }
    int lca(int x,int y)
    {
        if(x==y) return x;
        if((x=dfn[x])>(y=dfn[y])) swap(x,y);
        int l=__lg(y-x);
        return get(st[l][x+1],st[l][y-(1<<l)+1]);
    }
    int sol(int x,int y)
    {
        return dep[x]+dep[y]-dep[lca(x,y)]*2;
    }
    void merge(int x,int y)
    {
    	if(dep[x]>dep[y])swap(x,y);
    	int a=find(x),b=find(y);
    	fa[b]=a;
    	int aa[5],maxx=0;
    	aa[1]=la[a],aa[2]=lb[a],aa[3]=la[b],aa[4]=lb[b];
    	for(int i=1;i<=4;i++)
    	{
    		for(int j=i+1;j<=4;j++)
    		{
    			if(sol(aa[i],aa[j])>maxx)
    			{
    				maxx=sol(aa[i],aa[j]);
    				la[a]=aa[i];
    				lb[a]=aa[j];
				}
			}
		}
	}
    int ans(int x) 
    {
    	int y=find(x); 
    	return max(sol(x,la[y]),sol(x,lb[y]));
	}
}e; 

int main()
{
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)fa[i]=i,la[i]=i,lb[i]=i;
	for(int i=1;i<n;i++)
	{
		cin>>x[i]>>y[i];
		add(x[i],y[i]);
		add(y[i],x[i]);
	}
	e.init(); 
	for(int i=1;i<=m;i++)
	{
		int op,f;	
		cin>>op>>f;
		if(op==1) 
		{
			vis[f]=1;
			w[i]=f;
		}
		else 
		{
			w[i]=-f;
		}
	}
	for(int i=1;i<n;i++)
	{
		if(!vis[i])
		{
			e.merge(x[i],y[i]);
		}
	} 
	fill(pu+1,pu+1+m,-1); 
	for(int i=m;i>=1;i--)
	{
		if(w[i]>0)
		{
			e.merge(x[w[i]],y[w[i]]);
		}
		else
		{
			w[i]=-w[i];
			pu[i]=e.ans(w[i]);
		} 
	}
	for(int i=1;i<=m;i++)
	{
		if(pu[i]==-1)continue;
		cout<<pu[i]<<'\n';
	}
		 
	
	return 0;
}

A. 进击的巨人

提前感谢学长提供的公式的 \(markdown\)

容易发现 \(0\) 是分割线,所以考虑每个分割出来块,里面只有 \(1,?\),其期望值为

\[\begin{aligned} E(X) &= \sum_{0\le L < R\le n}(R - L)^k \times \tfrac{1}{2^{cnt_R - cnt_L}}\\ &= \sum_{0\le L < R\le n}\sum_{i = 0}^k\binom{k}{i}R^{k - i}\times (-L)^i\times\tfrac{1}{2^{cnt_R - cnt_L}}\\ &= \sum_{i = 0}^k\binom{k}{i}\sum_{0\le L < R\le n}R^{k - i}\times 2^{-cnt_R}\times (-L)^i\times 2^{cnt_L}\\ &= \sum_{i = 0}^k\binom{k}{i}\sum_{R = 1}^nR^{k - i}\times 2^{-cnt_R}\times (\sum_{L = 0}^{R - 1}(-L)^i\times 2^{cnt_L}) \end{aligned} \]

可以发现后面括号括起来的部分可以用前缀和提前预处理出来,所以整体复杂度在 \(O(nk)\) 左右

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int mod=998244353;
const int maxn=1e5+10;
using namespace std;
int n,k,cnt,ans,l[maxn],r[maxn],jc[100],jcny[100],sum[maxn];
int cheng[maxn];
char s[maxn];

void pre(){
	for(int i=(jc[0]=jcny[0]=jcny[1]=1)+1;i<=31;i++) 
		jcny[i]=1ll*jcny[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<=31;i++) 
		jc[i]=1ll*jc[i-1]*i%mod,jcny[i]=1ll*jcny[i-1]*jcny[i]%mod;
}

int qpow(int x,int y)
{
	int ans=1;
	while(y)
	{
		if(y&1)ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}

int c(int x,int y)
{
//	cout<<jcny[x]<<" "<<jcny[y]<<endl;
	return 1ll*jc[x]*jcny[x-y]%mod*jcny[y]%mod;
}
signed main()
{
	freopen("attack.in","r",stdin);
	freopen("attack.out","w",stdout); 
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>k;
	pre(); 
	cin>>s+1;
	int L=1,R=0;
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='0')
		{
			l[++cnt]=L-1,r[cnt]=R;
			L=i+1,R=i;
		}
		else
		{
			R++;
		}
	}
	if(s[n]!='0')l[++cnt]=L-1,r[cnt]=R;
//	for(int t=1;t<=cnt;t++) cout<<l[t]<<" "<<r[t]<<'\n'; 
	
	for(int t=1;t<=cnt;t++)
	{
		for(int i=l[t];i<=r[t];i++)
		{
			if(s[i]=='0')continue;
			if(s[i]=='?')sum[i]++;
			sum[i]+=sum[i-1];
		}
//		for(int i=l[t];i<=r[t];i++)
//			cout<<i<<" "<<sum[i]<<'\n';
	}
	char ss=s[1];
	for(int s=0;s<=k;s++)
	{
		int temp=0;
		for(int t=(ss=='0')+1;t<=cnt;t++)
		{
//			cout<<t<<"!"<<'\n';
			cheng[l[t]-1]=0; 
			for(int i=l[t];i<r[t];i++)
			{
				cheng[i]=(cheng[i-1]+1ll*qpow(-i,s)*qpow(2,sum[i])%mod)%mod;
//				cout<<cheng[i]<<'\n';
			}
			
			for(int i=l[t]+1;i<=r[t];i++)
			{
				temp=(temp+1ll*qpow(i,k-s)*qpow(qpow(2,sum[i]),mod-2)%mod*cheng[i-1]%mod)%mod;
//				cout<<temp<<" "<<cheng[i-1]<<"!"<<endl; 
			}
		}
		ans=(ans+temp*c(k,s)%mod+mod)%mod;	
//		cout<<temp<<"!"<<'\n';
	}
	
	cout<<ans;
	
	return 0;
} 
/*
3 1
01?
*/

B. Wallpaper Collection

\(f_{i, j}\) 表示当前考虑了前 \(i\) 行,其中第 \(i\) 行的初始位置为 \(j\) 时,最大的喜爱值之和。

转移枚举第 \(i + 1\) 行的初始位置 \(k\) ,发现在第 \(i + 1\) 行运动的区间满足 \(L\le \min(j, k), R\ge \max(j, k)\) ,此时转移为:

\[f_{i + 1, k} = \max_j(f_{i, j} + \max_{L\le \min(j, k), R\ge \max(j, k)}(\operatorname{sum}(i + 1, L, R))) \]

\(S_{i, j} = \sum_{k = 1}^{j}a_{i, k}, T_{i, j} = \sum_{k = j}^{n} a_{i, k}\) ,那么 \(\operatorname{sum}(i, L, R) = sum_i - S_{i, L - 1} - T_{i, R + 1}\)

\(\max\) 只需要维护 \(S, T\) 的前后缀 \(\min\) 即可。复杂度 \(O(n^3)\)

1000的数据这样还是有点悬啊,所以考虑优化,对于要求的 \(f_{i + 1, k}\) 的两维不能优化,这玩意也没有啥单调性,常规优化

不太可行,第三维考虑与 \(k\) 的关系,直接前后缀和优化一下就行

\[f_{i + 1, k} = \max_{j \le k}(f_{i, j} + sum_{i + 1} - MinS_{i + 1, j - 1} - MinT_{i + 1, k + 1}) \]

\[f_{i + 1, k} = \max_{k \le j}(f_{i, j} + sum_{i + 1} - MinT_{i + 1, j + 1} - MinS_{i + 1, k - 1}) \]

点击查看代码
#include<bits/stdc++.h>
#define ll long long
const int maxn=1010;
using namespace std;
int n,m,a[maxn][maxn];
ll f[maxn][maxn],sum[maxn],s[maxn][maxn],t[maxn][maxn],ans=-1e17;
ll tem[maxn],temp[maxn],mins[maxn][maxn],mint[maxn][maxn];
int main()
{
	freopen("WallpaperCollection.in","r",stdin);
	freopen("WallpaperCollection.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
			sum[i]+=a[i][j];
		}
	}
//	puts("<---------------------------->");
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			s[i][j]=s[i][j-1]+a[i][j];
			mins[i][j]=min(mins[i][j-1],s[i][j]);
//			cout<<s[i][j]<<" "; 
		}
//		cout<<endl;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=1;j--)
		{
			t[i][j]=t[i][j+1]+a[i][j];
			mint[i][j]=min(mint[i][j+1],t[i][j]);
		}
	}
	memset(f,0xcf,sizeof f); 
	for(int i=1;i<=m;i++)
		f[0][i]=0;
	for(int i=0;i<=n;i++)
	{
		fill(tem+1,tem+1+m,0);
		fill(temp+1,temp+1+m,0);
		tem[0]=temp[0]=-0x7f7f7f7f7f;
		for(int j=1;j<=m;j++)
		{
//			cout<<mins[i+1][j-1]<<endl; 
			tem[j]=max(tem[j-1],f[i][j]-mins[i+1][j-1]);
 		}
	 	for(int j=1;j<=m;j++)
		{
			f[i+1][j]=tem[j]+sum[i+1]-mint[i+1][j+1];
//			cout<<f[i+1][j]<<endl;
		}
		for(int j=m;j>=1;j--)
		{
			temp[j]=max(temp[j+1],f[i][j]-mint[i+1][j+1]);
		}
		for(int j=m;j>=1;j--)
		{
			f[i+1][j]=max(f[i+1][j],temp[j]+sum[i+1]-mins[i+1][j-1]);
		}
	}
	for(int i=1;i<=m;i++)
		ans=max(ans,f[n+1][i]);
	cout<<ans;
	
	return 0;
} 
/*

*/
posted @ 2024-08-18 21:33  _君の名は  阅读(32)  评论(0)    收藏  举报