CSP2024 前集训:多校A层冲刺NOIP2024模拟赛03
前言
T1 没想到正难则反,脑瘫了没敢用 bitset(复杂度擦边但卡常能过),T2 空间开大了挂了 \(100pts\),\(T3\) 是原。
T1 五彩斑斓
-
部分分 \(20pts\):\(O(n^4)\) 暴力。
-
部分分 \(20+?pts\):进行一些优化,极限数据下仍是 \(O(n^4)\)。
-
部分分 \(60\sim 100pts\):bitset 优化一下,\(O(\frac{n^4}{w})\)。
-
正解:
正难则反,求四个角都相同的个数再用总数减。
枚举行 \(i\) 和行 \(j\),若 \(a_{i,k}=a_{j,k}\) 则答案加上 \(\sum\limits_{h=1}^{k}[a_{i,k}=a_{j,k}]\),可以开桶处理,清空的时候循环 \(k\) 进行清空即可。
点击查看代码
#include<bits/stdc++.h> #define ll long long #define endl '\n' #define sort stable_sort using namespace std; const int N=410,M=1e6+10; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar_unlocked(); for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0; for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);} template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');} template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);} template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);} int n,m,a[N][N],cnt[M]; ll ans; signed main() { freopen("colorful.in","r",stdin),freopen("colorful.out","w",stdout); read(n,m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) read(a[i][j]); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) { for(int k=1;k<=m;k++) if(a[i][k]==a[j][k]) ans+=(++cnt[a[i][k]]); for(int k=1;k<=m;k++) if(a[i][k]==a[j][k]) cnt[a[i][k]]=0; } write(1ll*n*(n+1)*1ll*m*(m+1)/4-ans); }
T2 错峰旅行
-
部分分 \(20pts\):\(O(t-s)\) 暴力。
-
部分分 \(80pts\):发现只关心每个时间点有多少个城市可以去,发现 \([s,t]\) 可以划分成至多 \(2m+1\) 个区间,每个区间内的情况是完全一致的,所以动态开点线段树维护即可,复杂度 \(O(m\log(v))\),\(v=10^9\),但是空间开不下,开大了就会爆零。
-
正解:
发现离散化一下就可以直接差分做了,复杂度 \(O(m\log(m))\),瓶颈在于离散化。
点击查看代码
#include<bits/stdc++.h> #define ll long long #define endl '\n' #define sort stable_sort using namespace std; const int N=2e6+10,P=1e9+7; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar_unlocked(); for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0; for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);} template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');} template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);} template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);} int n,m,s,t,tot,ans,a[N],b[N],c[N],l[N],r[N]; int qpow(int a,int b) {int res=1; for(;b;b>>=1,a=1ll*a*a%P) if(b&1) res=1ll*res*a%P; return res;} signed main() { freopen("travel.in","r",stdin),freopen("travel.out","w",stdout); read(n,m,s,t),b[++tot]=s,b[++tot]=s+1,b[++tot]=t+1; for(int i=1,x;i<=m;i++) read(x,l[i],r[i]),b[++tot]=l[i],b[++tot]=r[i]+1; sort(b+1,b+1+tot),tot=unique(b+1,b+1+tot)-(b+1); for(int i=1;i<=m;i++) { c[lower_bound(b+1,b+1+tot,l[i])-b]++; c[lower_bound(b+1,b+1+tot,r[i]+1)-b]--; } for(int i=1;i<=tot;i++) a[i]=a[i-1]+c[i]; ans=n-a[1]; for(int i=3;i<=tot;i++) ans=1ll*ans*qpow(n-a[i-1],b[i]-b[i-1])%P; write(ans); }
T3 线段树
详见 NOIP2024模拟2。
T4 量子隧穿问题
发现是一棵基环树森林,只关心节点 \(n\) 所在的连通块。
对于树上的情况直接处理即可,有定义 \(p_i\) 表示点 \(i\) 有猫的概率,对于边 \((x,y)\),有 \(p_x'=p_x\times p_y,p_y'=p_y+p_x\times(1-p_y)\)。
考虑如何处理环,发现环上编号最小的边的状态存在后效性,遂钦定该条边连接两点的状态,然后按照树上处理,最后乘上这种状态的概率即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5010,P=1e9+7,inv2=500000004;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar_unlocked();
for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int T,n,ans,pos,p[N],to[N]; char s[N];
struct dsu
{
int f[N];
void init(int n) {for(int i=1;i<=n;i++) f[i]=i;}
int find(int x) {return x==f[x]?x:f[x]=find(f[x]);}
bool same(int x,int y) {return find(x)==find(y);}
bool merge(int x,int y) {return (x=find(x))==(y=find(y))?1:!(f[y]=x);}
}t1,t2;
signed main()
{
freopen("experiment.in","r",stdin),freopen("experiment.out","w",stdout);
for(read(T);T;T--)
{
read(n),scanf("%s",s+1); ans=0,t1.init(n),t2.init(n);
for(int i=1;i<=n;i++) read(to[i]),t1.merge(i,to[i]);
for(int i=n;i>=1;i--) if(t1.same(i,n)&&t2.merge(i,to[i])) pos=i;
for(int s1=0;s1<=1;s1++) for(int s2=0;s2<=1;s2++)
{
for(int i=1;i<=n;i++) p[i]=s[i]=='C'?1:(s[i]=='?'?inv2:0);
int q=1; for(int i=1,x,y;i<=n;i++)
{
if(i==pos)
{
q=1ll*(s1?p[pos]:1-p[pos]+P)*(s2?p[to[pos]]:1-p[to[pos]]+P)%P;
if(!q) break; p[i]=s1,p[to[i]]=s2;
}
p[i]=1ll*(x=p[i])*(y=p[to[i]])%P,p[to[i]]=(1ll*x*(1-y+P)%P+y)%P;
}
(ans+=1ll*q*p[n]%P)%=P;
}
for(int i=1;i<=n;i++) if(s[i]=='?') (ans*=2)%=P;
write(ans),puts("");
}
}
总结
- 注意正难则反。
- 擦边的复杂度也要打,肯定比纯暴力分高。
- 这个破 OJ 空间开多大算多大,开大直接爆零。
- 静态可差分问题完全可以不用线段树。