暑假集训CSP提高模拟17

暑假集训CSP提高模拟17

组题人: @joke3579

\(T1\) P222. 符号化方法初探 \(70pts\)

  • 原题: [ABC081D] Non-decreasing

  • 部分分

    • 测试点 \(1\) :输出样例 \(1\)
    • 测试点 \(11 \sim 15\) :由于 \(\{ a \}\) 非负,所以对 \(\{ a \}\) 作前缀和即可。
    • 随机 \(pts\) :乱搞。
  • 正解

    • \(\{ a \}\) 都是负数时,对 \(\{ a \}\) 作后缀和即可。
    • 问题来到了怎么让 \(\{ a \}\) 的符号都相同。
    • \(\{ a \}\) 中绝对值最大的一个数,让其他数都加上这个数即可。
    • 总次数为 \(2n-2\)
    点击查看代码
    int a[100010];
    int main()
    {
    	int n,maxx=0,pos=0,flag=1,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		if(abs(a[i])>maxx)
    		{
    			maxx=abs(a[i]);
    			pos=i;
    		}
    		if(i>=2)
    		{
    			flag&=((a[i]>=0&&a[i-1]>=0)||(a[i]<0&&a[i-1]<0));
    		}
    	}
    	if(flag==1)
    	{
    		cout<<n-1<<endl;
    		if(a[1]>=0)
    		{
    			for(i=2;i<=n;i++)
    			{
    				cout<<i-1<<" "<<i<<endl;
    			}
    		}
    		else
    		{
    			for(i=n-1;i>=1;i--)
    			{
    				cout<<i+1<<" "<<i<<endl;
    			}
    		}
    	}
    	else
    	{
    		cout<<2*n-2<<endl;
    		for(i=1;i<=n;i++)
    		{
    			if(i!=pos)
    			{
    				cout<<pos<<" "<<i<<endl;
    			}
    		}
    		if(a[pos]>=0)
    		{
    			for(i=2;i<=n;i++)
    			{
    				cout<<i-1<<" "<<i<<endl;
    			}
    		}
    		else
    		{
    			for(i=n-1;i>=1;i--)
    			{
    				cout<<i+1<<" "<<i<<endl;
    			}
    		}
    	}
    	return 0;
    }
    

\(T2\) P223. 无标号 Sequence 构造 \(40pts\)

  • 原题: luogu P10102 [GDKOI2023 提高组] 矩阵

  • 部分分

    • \(25 \sim 50pts\) :使用 \(O(n^{3})\) 的矩阵乘法暴力判断。
  • 正解

    • 发现仅需要判断相不相等,且只需要找到一个位置不同即可。
    • 考虑随机化,随机选择 \(1\) 个点进行判断的正确率太低,那么我们可以随机一个 \(1 \times n\) 的矩阵 \(D\) ,判断 \(D \times A \times B\) 是否等于 \(D \times C\)
    • 由秩-零化度定理可以知道这样判断错误的概率不超过 \(\frac{1}{998244353}\) ,不放心可以多随几次。
    点击查看代码
    const ll p=998244353;
    ll a[3010][3010],b[3010][3010],c[3010][3010],d[2][3010],e[2][3010],f[2][3010];
    int main()
    {
    	ll t,n,flag,i,j,k,h;
    	scanf("%lld",&t);
    	srand(time(0));
    	for(k=1;k<=t;k++)
    	{
    		flag=0;
    		scanf("%lld",&n);
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				scanf("%lld",&a[i][j]);
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				scanf("%lld",&b[i][j]);
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				scanf("%lld",&c[i][j]);
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			d[1][i]=rand();
    		}
    		for(i=1;i<=1;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				e[i][j]=0;
    				for(h=1;h<=n;h++)
    				{
    					e[i][j]=(e[i][j]+d[i][h]*a[h][j]%p)%p;
    				}
    			}
    		}
    		for(i=1;i<=1;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				f[i][j]=0;
    				for(h=1;h<=n;h++)
    				{
    					f[i][j]=(f[i][j]+e[i][h]*b[h][j]%p)%p;
    				}
    			}
    		}
    		for(i=1;i<=1;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				e[i][j]=0;
    				for(h=1;h<=n;h++)
    				{
    					e[i][j]=(e[i][j]+d[i][h]*c[h][j]%p)%p;
    				}
    			}
    		}
    		for(i=1;i<=1;i++)
    		{
    			for(j=1;j<=n;j++)
    			{
    				if(e[i][j]!=f[i][j])
    				{
    					flag=1;
    					break;
    				}
    			}
    			if(flag==1)
    			{
    				break;
    			}
    		}
    		if(flag==1)
    		{
    			printf("No\n");
    		}
    		else
    		{
    			printf("Yes\n");
    		}
    	}
    	return 0;
    }
    

\(T3\) P224. 无标号 Multiset 构造 \(5pts\)

  • 部分分
    • \(5pts\) :输出样例 \(1\)
  • 正解
    • 先不管连边,等最终把点都选出来后再进行连边并判断是否连通。

    • 观察到 \(k \in [1,5]\)

    • 对于每一列选择的方案只有 \(2^{k}\) 种,对于这 \(2^{k}\) 种状态在 \(n\) 列中的出现过的状态有 \(2^{2^{k}}\) 种,爆搜出这 \(2^{2^{k}}\) 种哪些是连通的。

    • 假设当前连通的一种状态里出现了 \(m\) 种选择状态,由 luogu P5824 十二重计数法 III 可知,总方案数为 \(\sum\limits_{i=1}^{\min(n,m)}(-1)^{m-i}\dbinom{m}{i}i^{n}\)

    • \(k=5\) 打表出连通状态,然后求解即可。

      点击查看打表代码
      ll jc[100],inv[100],jc_inv[100],vis[100],num[100];
      vector<ll>e;
      ll qpow(ll a,ll b,ll p)
      {
      	ll ans=1;
      	while(b)
      	{
      		if(b&1)
      		{
      			ans=ans*a%p;
      		}
      		b>>=1;
      		a=a*a%p;
      	}
      	return ans;
      }
      ll C(ll n,ll m,ll p)
      {
      	return (n>=m&&n>=0&&m>=0)?((jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p):0;
      }
      bool check(ll k)
      {
      	ll s=e[0],cnt=0;
      	for(int i=0;i<e.size();i++)
      	{
      		vis[e[i]]=1;
      	}
      	for(int j=1;j<=k;j++)//跑个 5,6 遍就差不多了
      	{
      		for(int i=0;i<e.size();i++)
      		{
      			if(e[i]&s)//有边相连
      			{
      				cnt+=vis[e[i]];
      				vis[e[i]]=0;
      				s|=e[i];
      			}
      		}
      	}
      	return cnt==e.size();
      }
      void dfs(ll pos,ll sum,ll n,ll k)
      {
      	if(pos==n)
      	{
      		e.clear();
      		for(ll i=0;i<=n-1;i++)
      		{
      			if((sum>>i)&1)
      			{
      				e.push_back(i);
      			}
      		}
      		if(__builtin_popcount(sum)==0||check(k)==1)
      		{
      			num[__builtin_popcount(sum)]++;
      		}
      	}
      	else
      	{
      		dfs(pos+1,sum,n,k);
      		dfs(pos+1,sum|(1<<pos),n,k);
      	}
      }
      int main()
      {
      	ll n,m,k,p,ans=0,sum=0,i;
      	cin>>n>>k>>p;
      	inv[1]=1;
      	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
      	for(i=2;i<=(1<<k);i++)
      	{
      		inv[i]=(p-p/i)*inv[p%i]%p;
      		jc[i]=jc[i-1]*i%p;
      		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
      	}
      	dfs(0,0,1<<k,k);
      	for(i=(1<<k);i>=1;i--)
      	{
      		num[i]+=num[i-1];//插状态为 0 的单独统计
      	}
      	for(m=0;m<=min(n,1ll<<k);m++)
      	{
      		sum=0;
      		for(i=0;i<=m;i++)
      		{
      			sum=(sum+qpow(-1,m-i,p)*C(m,i,p)*qpow(i,n,p)%p+p)%p;	
      		}
      		ans=(ans+sum*num[m]%p)%p;
      	}
      	cout<<ans<<endl;
      	return 0;
      }
      
      点击查看正解代码
      ll jc[100],inv[100],jc_inv[100],vis[100],num[100],num5[33]={1,31,375,3860,28845,162440,720491,2603950,7856260,20127820,44327130,84657300,141113700,206250800,265182000,300540120,300540190,265182525,206253075,141120525,84672315,44352165,20160075,7888725,2629575,736281,169911,31465,4495,465,31,1,0};
      vector<ll>e;
      ll qpow(ll a,ll b,ll p)
      {
      	ll ans=1;
      	while(b)
      	{
      		if(b&1)
      		{
      			ans=ans*a%p;
      		}
      		b>>=1;
      		a=a*a%p;
      	}
      	return ans;
      }
      ll C(ll n,ll m,ll p)
      {
      	return (n>=m&&n>=0&&m>=0)?((jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p):0;
      }
      bool check(ll k)
      {
      	ll s=e[0],cnt=0;
      	for(int i=0;i<e.size();i++)
      	{
      		vis[e[i]]=1;
      	}
      	for(int j=1;j<=k;j++)
      	{
      		for(int i=0;i<e.size();i++)
      		{
      			if(e[i]&s)
      			{
      				cnt+=vis[e[i]];
      				vis[e[i]]=0;
      				s|=e[i];
      			}
      		}
      	}
      	return cnt==e.size();
      }
      void dfs(ll pos,ll sum,ll n,ll k)
      {
      	if(pos==n)
      	{
      		e.clear();
      		for(ll i=0;i<=n-1;i++)
      		{
      			if((sum>>i)&1)
      			{
      				e.push_back(i);
      			}
      		}
      		if(__builtin_popcount(sum)==0||check(k)==1)
      		{
      			num[__builtin_popcount(sum)]++;
      		}
      	}
      	else
      	{
      		dfs(pos+1,sum,n,k);
      		dfs(pos+1,sum|(1<<pos),n,k);
      	}
      }
      ll ask(ll n,ll k,ll p,ll num[])
      {
      	ll ans=0,sum;
      	for(ll i=(1<<k);i>=1;i--)
      	{
      		num[i]=(num[i]+num[i-1])%p;
      	}
      	for(ll m=0;m<=min(n,1ll<<k);m++)
      	{
      		sum=0;
      		for(ll i=0;i<=m;i++)
      		{
      			sum=(sum+qpow(-1,m-i,p)*C(m,i,p)*qpow(i,n,p)%p+p)%p;	
      		}
      		ans=(ans+sum*num[m]%p)%p;
      	}
      	return ans;
      }
      int main()
      {
      	ll n,m,k,p,i;
      	cin>>n>>k>>p;
      	inv[1]=1;
      	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
      	for(i=2;i<=(1<<k);i++)
      	{
      		inv[i]=(p-p/i)*inv[p%i]%p;
      		jc[i]=jc[i-1]*i%p;
      		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
      	}
      	if(k==5)
      	{
      		cout<<ask(n,k,p,num5);
      	}
      	else
      	{
      		dfs(0,0,1<<k,k);
      		cout<<ask(n,k,p,num)<<endl;
      	}
      	return 0;
      }
      

\(T4\) P225. 有限制的构造 \(40pts\)

  • 原题: [ABC364E] Maximum Glutton
  • 考虑求出要求玩过的游戏的画面质量之和 \(\le A\) 且不可玩度之和 \(\le B\) 的最多游戏数,然后多玩一个即可(若还有剩的)。
  • 部分分
    • \(25 \sim 40pts\) :设 \(f_{i,j,k}\) 表示处理到第 \(i\) 个游戏时玩过的游戏的画面质量之和 \(\le j\) 且不可玩度之和 \(\le k\) 的最多游戏数,状态转移方程为 \(f_{i,j,k}=\max(f_{i-1,j,k},f_{i-1,j-a_{i},k-b_{i}}+1)\),跑一遍二维 \(01\) 背包 \(DP\) ,稍微压一下空间。

      点击查看代码
      int w[100],v[100];
      short f[10010][6510];
      int main()
      {
      	int n,a,b,i,j,k;
      	short sum;
      	cin>>n>>a>>b;
      	for(i=1;i<=n;i++)
      	{
      		cin>>w[i]>>v[i];
      		if(a<b)
      		{
      			swap(w[i],v[i]);
      		}
      	}
      	if(a<b)
      	{
      		swap(a,b);
      	}
      	for(i=1;i<=n;i++)
      	{
      		for(j=a;j>=w[i];j--)
      		{
      			for(k=b;k>=v[i];k--)
      			{
      				sum=f[j-w[i]][k-v[i]]+1;
      				f[j][k]=max(f[j][k],sum);
      			}
      		}
      	}
      	cout<<f[a][b]+(f[a][b]!=n)<<endl;
      	return 0;
      }
      
  • 正解
    • AT_dp_e Knapsack 2 | CF922E Birds 的经验,考虑优化状态设计。

    • \(f_{i,j,k}\) 表示前 \(i\) 个游戏中玩了 \(j\) 个游戏,画面质量之和 \(\le k\) 时不可玩度之和的最小值,状态转移方程为 \(f_{i,j,k}=\min(f_{i-1,j,k},f_{i-1,j-1,k-a_{i}}+b_{i})\)

      点击查看代码
      int w[100],v[100],f[2][100][10010];
      int main()
      {
      	int n,a,b,i,j,k;
      	cin>>n>>a>>b;
      	memset(f,0x3f,sizeof(f));
      	for(i=1;i<=n;i++)
      	{
      		cin>>w[i]>>v[i];
      	}
      	f[0][0][0]=0;
      	for(i=1;i<=n;i++)
      	{
      		for(j=0;j<=i;j++)
      		{
      			for(k=0;k<=a;k++)
      			{
      				f[i&1][j][k]=f[(i-1)&1][j][k];
      				if(j-1>=0&&k-w[i]>=0)
      				{
      					f[i&1][j][k]=min(f[i&1][j][k],f[(i-1)&1][j-1][k-w[i]]+v[i]);
      				}
      			}
      		}
      	}
      	for(i=n;i>=0;i--)
      	{
      		for(k=0;k<=a;k++)
      		{
      			if(f[n&1][i][k]<=b)
      			{
      				cout<<i+(i!=n)<<endl;
      				return 0;
      			}
      		}
      	}
      }
      

总结

  • \(T1\) 下发了 check.cpp 并讲解了在 \(Linux\) 下怎么给可运行文件 check 加权限 chmod +x checker
  • \(T3\) 读假题了,以为是一次选择一对点对,然后进行连边。

后记

  • 熟悉的公告。

posted @ 2024-08-10 15:47  hzoi_Shadow  阅读(56)  评论(0编辑  收藏  举报
扩大
缩小