暑假集训CSP提高模拟6
暑假集训CSP提高模拟6
组题人: @KafuuChinocpp | @Chen_jr
\(T1\) P162. 花间叔祖 \(100pts\)
-
原题: [ARC148A] mod M
-
不难发现,最终答案只有 \(1\) 和 \(2\) 两种取值。
-
若 \(\exists p \ge 2,a_{1} \equiv a_{2} \equiv a_{3} \dots \equiv a_{n} \pmod {p}\) ,则最终答案为 \(1\) ;否则,答案为 \(2\) 。
-
又因为 \(x \equiv y \pmod p\) 当且仅当 \(p|(x-y)\) 。进而得到 \(\begin{cases} p|(a_{2}-a_{1}) \\ p|(a_{3}-a_{2}) \\ \dots \\ p|(a_{n}-a_{n-1}) \end{cases}\) ,取 \(p=\gcd(a_{2}-a_{1},a_{3}-a_{2}, \dots ,a_{n}-a_{n-1})\) 即可。
点击查看代码
int a[200010]; int gcd(int a,int b) { return b?gcd(b,a%b):a; } int main() { int n,d=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } sort(a+1,a+1+n); for(i=2;i<=n;i++) { d=gcd(d,a[i]-a[i-1]); } cout<<((d==1)?2:1)<<endl; return 0; }
-
赛时写得比较唐,取 \(a_{2}-a _{1},a_{3}-a_{2}, \dots ,a_{n}-a_{n-1}\) 的最小非 \(1\) 公因数,质因数分解后开桶记录质因子出现次数。因为 \(1 \times 10^{9}\) 内质因子数量不超过 \(10\) 所以时空复杂度可以接受。
点击查看代码
int a[200010],prime[200010],c[200010],len=0; void divede(int x) { for(int i=2;i*i<=x;i++) { if(x%i==0) { len++; prime[len]=i; c[len]++; while(x%i==0) { x/=i; } } } if(x>1) { len++; prime[len]=x; c[len]++; } } int main() { int n,flag=0,x,pos=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=2;i<=n;i++) { if(a[i]!=a[i-1]) { pos=i; break; } } if(pos!=0) { divede(abs(a[pos]-a[pos-1])); for(i=2;i<=n;i++) { if(i!=pos) { x=abs(a[i]-a[i-1]); for(j=1;j<=len;j++) { c[j]+=(x%prime[j]==0); } } } for(i=1;i<=len;i++) { flag|=(c[i]==n-1); } } else { flag=1; } cout<<((flag==0)?2:1)<<endl; return 0; }
\(T2\) P150. 合并r \(10pts\)
-
部分分
- \(10pts\) :当 \(n=1\) 时,输出
1
。
- \(10pts\) :当 \(n=1\) 时,输出
-
设 \(f_{i,j}\) 表示填到第 \(i\) 个数时元素和为 \(j\) 的方案数,此时可以选择填 \(1\) 或让先前填的数都除以 \(2\) ,故状态转移方程为 \(f_{i,j}=f_{i-1,j-1}+f_{i,2j}\) ,边界为 \(f_{0,0}=1\) 。
点击查看代码
const ll p=998244353; ll f[5010][5010]; int main() { ll n,k,i,j; cin>>n>>k; f[0][0]=1; for(i=1;i<=n;i++) { for(j=i;j>=1;j--) { f[i][j]=f[i-1][j-1]; if(2*j<=i) { f[i][j]=(f[i][j]+f[i][2*j])%p; } } } cout<<f[n][k]<<endl; return 0; }
\(T3\) P139. 回收波特 \(47pts\)
-
部分分
-
\(47pts\) : \(O(nm)\) 暴力枚举即可。
点击查看代码
ll x[300010],ans[300010]; int main() { ll n,m,d,i,j; scanf("%lld%lld",&n,&m); for(i=1;i<=n;i++) { scanf("%lld",&x[i]); } for(i=1;i<=m;i++) { scanf("%lld",&d); for(j=1;j<=n;j++) { if(ans[j]==0) { x[j]+=((x[j]>0)?-d:d); if(x[j]==0) { ans[j]=i; } } } } for(i=1;i<=n;i++) { if(ans[i]!=0) { printf("Yes %lld\n",ans[i]); } else { printf("No %lld\n",x[i]); } } return 0; }
-
-
正解
- 充分发扬人类智慧,我们发现任一时刻,若两个点关于原点对称则它们此后的位置一定一直关于原点对称。
- 波特是动的,但原点是不动的。考虑反过来,让原点动,波特不动。
- 考虑维护值域,预处理出所有点的答案,最后进行询问。
- 每次原点移动后将区间分成在原点左侧、在原点上、在原点右侧三个部分,类似启发式合并,将小区间对称到大区间里,建边方便继承答案,然后将小区间删掉。由于每个点至多被删除 \(1\) 次所以处理操作时间复杂度为 \(O(V)\) 。
- 最后再 \(DFS\) 一遍处理出没删掉的点的答案。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[2000010],x[2000010],dfn[2000010],pos[2000010],vis[2000010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x) { vis[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { if(dfn[x]==0) { pos[e[i].to]=-pos[x];//不能停,坐标取反 } else { dfn[e[i].to]=dfn[x];//能停,直接继承 } dfs(e[i].to); } } } int main() { int n,m,l=1,r=1000000,d,mid=0,dir=0,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x[i]; } for(i=1;i<=m;i++) { cin>>d; mid+=(dir==0)?d:-d; if(l<=mid&&mid<=r) { dfn[mid]=i; if(mid-1-l+1<=r-(mid+1)+1) { for(j=l;j<=mid-1;j++) { add(mid*2-j,j); } l=mid+1; dir=0; } else { for(j=mid+1;j<=r;j++) { add(mid*2-j,j); } r=mid-1; dir=1; } } else { dir=(mid>r); } } for(i=l;i<=r;i++) { if(dfn[i]==0) { pos[i]=i-mid; } dfs(i); } for(i=1;i<=l-1;i++) { if(dfn[i]!=0) { dfs(i); } } for(i=r+1;i<=1000000;i++) { if(dfn[i]!=0) { dfs(i); } } for(i=1;i<=n;i++) { if(dfn[x[i]]==0) { cout<<"No "<<pos[x[i]]<<endl; } else { cout<<"Yes "<<dfn[x[i]]<<endl; } } return 0; }
\(T4\) T3342. 斗篷 \(0pts\)
-
发现数据的输入格式是有一定限制的。
-
只考虑统计形如△的三角形,然后将整个图翻转过来求解。
-
预处理出 \(ld_{x,y},rd_{x,y},l_{x,y}\) 分别表示 \((x,y)\) 向左上/右上/左边所能延伸的最长距离。
-
枚举 \((x,y)\) 作为三角形的右下角,则能进行转移的三角形长度 \(i \in [1,\min(l_{x,y},ld_{x,y})]\) 若满足 \(rd_{x-i,y} \ge i\) 则对答案产生 \(1\) 的贡献。
- 自觉忽略
---
的影响。
- 自觉忽略
-
观察到 \(\forall i \in [1,rd_{x,y}],rd_{x+i-i,y}=rd_{x,y} \ge i\) ,即 \((x,y)\) 会对 \((k,y)(k \in [x+1,x+rd_{x,y}])\) 产生 \(1\) 的贡献,进行差分,扫描线维护一下即可。具体地,在 \((x,y)\) 的位置进行加一,在 \((x+rd_{x,y})\) 进行减一,
vector
存一下要减哪些数的位置。点击查看代码
struct BIT { ll c[12010]; void init() { memset(c,0,sizeof(c)); } ll lowbit(ll x) { return (x&(-x)); } void update(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; short ld[6010][12010],rd[6010][12010],l[6010][12010]; char s1[6010][12010],s2[6010][12010]; vector<ll>d[12010]; string t; ll ask(char s[6010][12010],ll n,ll m) { memset(ld,0,sizeof(ld)); memset(rd,0,sizeof(rd)); memset(l,0,sizeof(l)); ll ans=0,i,j,k; for(i=1;i<=n;i+=2) { T.init(); for(j=(s[i][1]==' '?3:1),k=1;j<=m;j+=4,k++) { ld[i][j]=(s[i-1][j-1]!=' '&&i-2>=0&&j-2>=0)?ld[i-2][j-2]+1:0; rd[i][j]=(s[i-1][j+1]!=' '&&i-2>=0&&j+2<=m)?rd[i-2][j+2]+1:0; l[i][j]=(s[i][j-1]!=' '&&j-4>=0)?l[i][j-4]+1:0; ans+=T.getsum(k)-T.getsum(k-min(ld[i][j],l[i][j])-1); T.update(m,k,1); d[k+rd[i][j]].push_back(k); while(d[k].empty()==0) { T.update(m,d[k].back(),-1); d[k].pop_back(); } } } return ans; } int main() { ll r,c,n,m,i,j; cin>>r>>c; n=2*r-1; m=2*c-1; for(i=0;i<=n;i++) { getline(cin,t); while(t.size()<m) { t+=' '; } s1[i][0]=' '; for(j=0;j<=m-1;j++) { s1[i][j+1]=t[j]; } } for(i=1;i<=n;i++) { s2[i][0]=' '; for(j=1;j<=m;j++) { s2[i][j]=s1[n-i+1][m-j+1]; } } cout<<ask(s1,n,m)+ask(s2,n,m)<<endl; return 0; }
总结
- \(T1\) 没想到直接取 \(\gcd\) ,导致要质因数分解。
后记
-
题解名称为
hustDelov.md
。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18319969,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。