暑假集训CSP提高模拟17
暑假集训CSP提高模拟17
组题人: @joke3579
\(T1\) P222. 符号化方法初探 \(70pts\)
-
部分分
- 测试点 \(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\)
-
部分分
- \(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\) 读假题了,以为是一次选择一对点对,然后进行连边。
后记
-
熟悉的公告。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18352268,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。