CSP模拟day8
A. 模板题
30pts
枚举ai,bj 预处理{c} O(nm)
45pts
60pts
不用前缀和用树状数组,多个log
数组没开两倍
100pts
ci=∑ajbi-j
考虑区间转前缀solve(r)-solve(l-1)
由于q很小,可以预处理b的前缀和,每次枚举i,O(n)
0pts
注意模数相加会爆int,记得开long long(真不是故意卡成0分的)不要那么自信,试一下我给的大样例啊!!!
变量名污染CE的
NTT打挂了

#include<bits/stdc++.h> using namespace std; typedef unsigned long long u64; #define int long long const int MAX=6e6+10; const int p=1145141999; int a[MAX],b[MAX<<1]; int n,m,q,w; u64 seed; u64 rnd() { u64 x = seed; x ^= x << 13; x ^= x >> 7; x ^= x << 17; return seed = x; } inline int read(){ int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();} return x*f; } inline int work(int x){ if(x<0) return 0; int res=0; for(int i=0;i<=min(x,n);++i) res=(res+a[i]*b[x-i]%p)%p; return res; } signed main(){ scanf("%d%d%llu%lld",&n,&m,&seed,&w); scanf("%d",&q); for(int i=0;i<=n;++i)a[i]=rnd()%w; for(int i=0;i<=m;++i)b[i]=rnd()%w; for(int i=1;i<=m+n;++i) b[i]=(b[i]+b[i-1])%p; int l,r; for(int i=1;i<=q;++i){ l=read();r=read(); printf("%lld\n",(work(r)-work(l-1)+p)%p); } }
B. THUSC
60pts
在前两个子任务放了n=1,某些打法需要特判
70pts
对每一个斜率找直线时,对所有点计算b,再统计相同b的个数
O(num*nlog)
用double记录斜率,损失精度,最后一个子任务有卡精度的点
100pts
题目描述非常没有人性清晰,对所有x,y排序后有多少不同的序列
显然kx,ky的序列是相同的,同理x/y相近的序列会有很多相同
设B=ax+by,b=(B-ax)/y=-(x/y)a+B/y
形如Y=kX+b(x,y已经固定,所以B/y为常数,及时y=ky时绝对大小不同,但相对大小相同)
以ai为横坐标,bi为纵坐标,斜率为-x/y的斜线经过每个点,以纵截距排列
以样例为例,当某一x,y时
排名为1,2,3,4,5
于是,我们计算每两点之间斜率,以Δx/gxd(Δx,Δy),Δy/gxd(Δx,Δy)的形式保证精度,并用(化简后的)Δy*x1+Δx*y1记录为哪一条直线
一点计算:y1=-(x/y)x1+b y1*y=-x*x1+b*y y1*y+x1*x=b*y b*y在确定x,y时为常数
分为两种情况
1、同一直线,线上的点可以任意排名,方案数为线上点数量的阶乘
2、斜率相同,即x,y相同,对不同的直线方案相乘,∏cnt!
而不同时经过两点的斜率,缓慢移动,使它经过两点,此时的若干排名包括之前斜率的排名
如上图排名包括在
因此每个斜率还会算上略大于略小于它的斜率的排名
每两个斜率之间,夹着的斜率排名会被算两次
如上图排名1,4,3,2,5在下图中也会出现
设一共有num条不同的斜率,最后方案为(∑∏cnt!)-(num-1)

#include<bits/stdc++.h> using namespace std; #define int long long const int MAX=1010; const int mod=998244353; int n,dertx,derty,x,cnt,num,res,ans,f[MAX]; pair<int,int>a[MAX]; struct node{ int x,y,b; friend bool operator==(const node &a,const node &b){ return a.x==b.x&&a.y==b.y&&a.b==b.b; } } c[MAX*MAX]; inline int read(){ int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();} return x*f; } int gcd(int a,int b){ if(!b) return a; return gcd(b,a%b); }inline bool cmp(node a,node b){ if(a.x==b.x&&a.y==b.y) return a.b<b.b; if(a.x==b.x) return a.y<b.y; return a.x<b.x; } signed main(){ // freopen("1.txt","r",stdin); n=read();f[0]=1; for(int i=1;i<=n;++i) f[i]=f[i-1]*i%mod; for(int i=1;i<=n;++i) a[i]={read(),read()}; sort(a+1,a+n+1); for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j){ dertx=a[j].first-a[i].first;derty=a[i].second-a[j].second; if(!dertx||derty<=0) continue; x=gcd(dertx,derty);dertx/=x;derty/=x; c[++cnt]={dertx,derty,derty*a[i].first+dertx*a[i].second}; } sort(c+1,c+cnt+1,cmp);int j; for(int i=1;i<=cnt;i=j){ j=i;++num;res=1; while(j<=cnt&&c[j].x==c[i].x&&c[j].y==c[i].y){ int k=j; while(k<=cnt&&c[k]==c[j]) ++k; res=res*f[(int)sqrt(k-j<<1)+1]%mod; j=k; }ans=(ans+res)%mod; }ans=(ans-num+1)%mod; printf("%d",ans); }
C. 8ady
Sub1
next_permutation本来就是从小到大枚举的,不用排序
扫到第k个符合{b}的{a}就结束,否则会T成0
不开O2也会T
Sub4
bi为a1,……amin(i+m-1,n)中最小且不在b1,……bi-1的数
{b}最后m-1个数,为n-m+2,……n-1,n,
反证,若这m-1个数出现在之前,则要求它是在{a}某一段长为m的序列中第m大的数,显然没有m-1个数比它们大。且变化后同样
若max(b1,……bi-1)>bi,则bi为ai+m-1
反证,若bi出现在ai+m-1前,则它会出现在bi前
把这些bi提出,最后剩下{b}单调递增,依次填入未填的空,就是最小排列
Sub2
每个数出现可能有min(i+m-1,n),在填入前没有相对前后,不知道谁会占用谁的位置
转化为n变小,m,k不变的子问题
枚举i位填入j的方案数,递增j
注意固定i,j后,计算方案数枚举未填入的非j的数的方案时,填入一位后面的数少一个可能位置
O(n^3)
100pts
排列的方案数为阶乘形式,20!>1e18,所以可以直接固定前n-20项为ai=i的前缀
后20个数有n-20个可能位置已经被占用,方案数要减去
O(log^3+n)

#include<bits/stdc++.h> using namespace std; const int MAX=1e6+10; #define int long long int n,m,K,ab,a[MAX],b[MAX],ans[MAX],d[22],cnt; inline int read(){ int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();} return x*f; } signed main(){ // freopen("in.txt","r",stdin); n=read();m=read();K=read(); for(int i=1;i<=n;++i){ ab=read(); if(b[cnt]>ab) ans[i+m-1]=ab; else b[++cnt]=ab; }int p=max(0ll,cnt-20); for(int i=1;i<=p;++i) a[i]=i; for(int i=1;i<=cnt-p;++i) d[i]=min(m+i+p-1,cnt)-p; for(int i=1;i<=cnt-p;++i){ for(int j=1;j<=cnt-p;++j) --d[j]; for(int j=1;j<=cnt-p;++j){ if(d[j]<0) continue; int res=1,c=0; for(int k=1;k<=cnt-p;++k){ if(d[k]<0||k==j) continue; res*=d[k]-c;++c; if(res>=K) break; }if(res>=K){ a[i+p]=j+p;d[j]=-1;break; }K-=res; } }cnt=0; for(int i=1;i<=n;++i){ if(ans[i]) printf("%d ",ans[i]); else printf("%d ",b[a[++cnt]]); } }
D. 白子说话
1/2的意思每个黑子在初始时1/2概率存在
对于所有可能的白点1/4概率度数为奇
插头dp求欧拉图可能个数
下以x+y偶为例
x奇时,连接(i-1,j)(i+1,j)
x偶时,连接(i,j-1)(i,j+1)
每次插入(2x,2y),(2x-1,2y+1),对一列3个白点分别讨论是否存在,度数是否为奇,相互是否连通,所在连通块是否有奇数点
如插入(2,2),(1,3) 状态存的点为(0,5)(2,1)(2,3)
考虑如何通过枚举(2x,2y)(2x-1,2y+1)是否存在,确定转移到的状态,积累方案数
细节解释在代码
#include<bits/stdc++.h> using namespace std; const int MAX=610; const int mod=998244353; #define int long long #define dp123 dp[x][y][s1][s2][s3][s4] int n,c[110][9],b[110][9],f[MAX],inv4=748683265,fa[9],id[9],x,y; int dp[55][7][10][10][10][10]; map<pair<int,int>,bool>mp; inline int read(){ int x=0,f=1;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();} return x*f; } int father(int x){ if(fa[x]==x) return x; return fa[x]=father(fa[x]); }inline void merge(int x,int y){ x=father(x);y=father(y); if(x!=y) fa[x]=y; } inline int solve(int a[][9]){ memset(dp,0,sizeof(dp));mp.clear(); for(int i=0;i<100;++i) for(int j=0;j<6;++j) if(a[i][j]){ if(i&1) mp[{i-1,j}]=mp[{i+1,j}]=1; else mp[{i,j-1}]=mp[{i,j+1}]=1; } int ans=mp.size()*f[n]%mod*inv4%mod,sum=n; dp[0][0][0][0][0][0]=1; for(int x=0;x<=51;++x) for(int y=0;y<3;++y){ sum-=(x&&a[(x<<1)-1][y<<1|1])+a[x<<1][y<<1]; for(int s1=0;s1<8;++s1) for(int s2=0;s2<8;++s2) for(int s3=0;s3<8;++s3) for(int s4=0;s4<8;++s4) if(dp123){ for(int p=0;p<=(x&&a[(x<<1)-1][y<<1|1]);++p) for(int q=0;q<=a[x<<1][y<<1];++q){ int ss1=s1,ss2=s2,ss3=s3,ss4=s4; if(ss1>>y&1) ss1^=1<<y;//删去存储的原y的状态 ss1|=((p|q)<<y)|((q&&y)<<y-1);//p或q存在时,y存在;q存在时,y-1存在 if(ss2>>y&1) ss2^=1<<y; ss2^=((p^q)<<y)|((q&&y)<<y-1);//存在一个p或q,y多一条边 //0 1 2为原状态存的点,3为现在的y点 //根据原状态和pq连边,并查集判连通 for(int i=0;i<=3;++i) fa[i]=i,id[i]=i; id[y]=3; if(s3&1) merge(0,1); if((s3>>1)&1) merge(0,2); if((s3>>2)&1) merge(1,2); if(p) merge(y,3); if(q&&y) merge(y-1,3); ss3=(father(id[0])==father(id[1]))|((father(id[0])==father(id[2]))<<1) |((father(id[1])==father(id[2]))<<2); if(ss4>>y&1) ss4^=1<<y; //(!y&&q) (2x,-1)只有这一条边,度数为奇且与y连通 //当p存在,即原y与现y连通, //(s4>>y&1)原y所在连通块有奇数点;或!(s2>>y&1)原y之前为偶数点,现度数为奇时,现y所在连通块有奇数点 //注意现y的奇偶性还未确定,不能ss4|=(ss2>>y&1)<<y; if((!y&&q)||(p&&((s4>>y&1)||(!(s2>>y&1))))) ss4|=1<<y; //某一点连通块有奇数点时,与它相连的点的连通块都有奇数点 for(int i=0;i<3;++i) for(int j=i+1;j<3;++j) if(father(id[i])==father(id[j])){ ss4|=(ss4>>i&1)<<j; ss4|=(ss4>>j&1)<<i; } //原y为奇数点时,与它相连的点的连通块都有奇数点 //注意p&&(!(s2>>y&1))时,原y也为奇数点,但在上面在现y里算过 if(!p&&(s2>>y&1)) for(int i=0;i<=2;++i) ss4|=(father(id[i])==father(y))<<i; //向下一状态转移 if(y==2) dp[x+1][0][ss1][ss2][ss3][ss4]=(dp[x+1][0][ss1][ss2][ss3][ss4]+dp123)%mod; else dp[x][y+1][ss1][ss2][ss3][ss4]=(dp[x][y+1][ss1][ss2][ss3][ss4]+dp123)%mod; int fg=0; for(int i=0;i<=3;++i) if(i^y) fg|=(father(i)==father(y)); //当原y存在且度数为奇且与现状态3点不连通且所在连通块没有奇数点 //即原y所在连通图为欧拉图,它为此图的最后一个点,此时算入答案,不会算重 if((s1>>y&1)&&(!(s2>>y&1))&&(!fg)&&(!(s4>>y&1))) ans=(ans+dp123*f[sum]%mod)%mod; } } } return ans; } signed main(){ n=read();f[0]=1; for(int i=1;i<=n;++i) f[i]=(f[i-1]+f[i-1])%mod; for(int i=1;i<=n;++i){ x=read();y=read(); if((x+y)&1) b[x][5-y]=1; else c[x][y]=1; }printf("%d",(solve(c)+solve(b))%mod); }
先看官方题解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)