[NOIP模拟23]题解
中间鸽了好几篇啊QAQ……有时间再补吧……
A.mine
sbdp,考场上写的巨麻烦不过还是能A的(虽然MLE了……每一维都少开1就A掉了555)。设$dp[i][j][k]$为枚举到第i位,第i位是j,第i-1位是k的方案数。j和k都是0~3的整数,分别代表有前后几个雷/就是雷。
然后大力分类讨论即可
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=1e6+5; typedef long long ll; const ll mod=1e9+7; char s[N]; int str[N]; ll dp[N][5][5];//到第i位 第i位是j 第i-1位是k //0 0 //1 1 //2 2 //3 * int len; void mmod(ll &x) { while(x>=mod)x=x-mod; } bool isbomb(int x) { if(str[x]==4||str[x]==3)return 1; return 0; } bool judge() { for(int i=1;i<=len;i++) { if(str[i]==4)continue; if(str[i]==1&&(str[i-1]==2||(str[i-1]==3&&str[i+1]==3)||(!isbomb(i-1)&&!isbomb(i+1))))return 0; if(str[i]==2&&(!isbomb(i-1)||!isbomb(i+1)))return 0; } return 1; } int main() { scanf("%s",s+1);len=strlen(s+1); for(int i=1;i<=len;i++) if(s[i]=='2')s[i-1]=s[i+1]='*'; if(len==1) { if(s[1]=='*'||s[1]=='0')puts("1"); else puts("0"); return 0; } for(int i=1;i<=len;i++) { if(s[i]=='0')str[i]=0; else if(s[i]=='1')str[i]=1; else if(s[i]=='2')str[i]=2; else if(s[i]=='*')str[i]=3; else if(s[i]=='?')str[i]=4; } if(!judge()) { puts("0"); return 0; } if(str[1]!=4) { if(str[1]==1)dp[1][1][0]=1; else if(str[1]==3)dp[1][3][1]=1; else if(str[1]==0)dp[1][0][0]=1; } else dp[1][1][0]=dp[1][3][1]=dp[1][0][0]=1; for(int i=2;i<=len;i++) { // cout<<str[i]<<endl; if(str[i]==0||str[i]==4) { mmod(dp[i][0][1]+=dp[i-1][1][3]); mmod(dp[i][0][0]+=(dp[i-1][0][1]+dp[i-1][0][0])); } if(str[i]==1||str[i]==4) { mmod(dp[i][1][3]+=dp[i-1][3][1]+dp[i-1][3][2]+dp[i-1][3][3]); mmod(dp[i][1][0]+=dp[i-1][0][1]+dp[i-1][0][0]); mmod(dp[i][1][1]+=dp[i-1][1][3]); } if(str[i]==2||str[i]==4) { mmod(dp[i][2][3]+=dp[i-1][3][2]+dp[i-1][3][1]+dp[i-1][3][3]); } if(str[i]==3||str[i]==4) { mmod(dp[i][3][1]+=dp[i-1][1][0]+dp[i-1][1][1]); mmod(dp[i][3][2]+=dp[i-1][2][3]); mmod(dp[i][3][3]+=dp[i-1][3][1]+dp[i-1][3][2]+dp[i-1][3][3]); } } ll ans=0; if(str[len]!=4&&str[len-1]!=4)ans=dp[len][str[len]][str[len-1]]; else if(str[len]==4&&str[len-1]!=4) { for(int i=0;i<=3;i++) { int j=str[len-1]; if(i==0&&(j==3||j==2))continue; if(i==1&&(j!=3))continue; if(i==2)continue; if(i==3&&j==0)continue; mmod(ans+=dp[len][i][str[len-1]]); } } else if(str[len]!=4&&str[len-1]==4) { for(int j=0;j<=3;j++) { int i=str[len]; if(i==0&&(j==3||j==2))continue; if(i==1&&(j!=3))continue; if(i==2)continue; if(i==3&&j==0)continue; mmod(ans+=dp[len][str[len]][j]); } } else { for(int i=0;i<=3;i++) for(int j=0;j<=3;j++) { if(i==0&&(j==3||j==2))continue; if(i==1&&(j!=3))continue; if(i==2)continue; if(i==3&&j==0)continue; mmod(ans+=dp[len][i][j]); } } cout<<ans<<endl; return 0; }
一开始那个$judge()$是判不合法情况。A掉之后突然发现我的程序好像遇到一开始就不合法的情况还是会有方案,所以特判了一下。
B.water
一个块的最终高度(初始高度+积水高度)就是从这个块走出矩形的路径上最大值的最小值,即把路径定义为权值取max而非权值和后的最短路。那么就直接相邻块建图,边界块都连到一个源点上,之后从源点跑dj即可。有点像网络流。
#include<cstdio> #include<iostream> #include<cstring> #include<queue> #include<map> #define pa pair<int,int> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } const int N=305,M=500005; const int dx[5]={0,1,0,-1}, dy[5]={1,0,-1,0}; int n,m; int a[N][N]; int to[M],nxt[M],head[M],len[M],tot; void add(int x,int y,int z) { to[++tot]=y; nxt[tot]=head[x]; len[tot]=z; head[x]=tot; } int id(int i,int j){return (i-1)*m+j;} int dis[M],vis[M]; void dj(int s) { memset(dis,0x3f,sizeof(dis)); priority_queue<pa> q; dis[s]=0;q.push(make_pair(0,s)); while(!q.empty()) { int x=q.top().second;q.pop(); if(vis[x])continue; vis[x]=1; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(dis[y]>max(dis[x],len[i])) { dis[y]=max(dis[x],len[i]); q.push(make_pair(-dis[y],y)); } } } } int main() { n=read();m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { for(int k=0;k<4;k++) { int nx=i+dx[k],ny=j+dy[k]; if(nx<1||nx>n||ny<1||ny>m)add(n*m+1,id(i,j),max(a[i][j],0)),add(id(i,j),n*m+1,max(a[i][j],0)); else add(id(i,j),id(nx,ny),max(a[i][j],a[nx][ny])); } } dj(n*m+1); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) printf("%d ",dis[id(i,j)]-a[i][j]); putchar('\n'); } return 0; }
C.gcd
披着数据结构题的毒瘤数论。
需要维护三个量:
$f[]$:选中集合中gcd为i的数对个数
$g[]$:选中集合中gcd为i的倍数的数对个数
$s[]$:选中集合中i的倍数的个数
首先$s[]$的值是很好得到的,每次加入数时枚举它的因子$i$,$s[i]++$即可。
那$s[]$的意义何在呢?它可以帮助我们快速得到$g[]$。显然$g[i]=C_{s[i]}^2$。
又因为$g[i]=\sum \limits_{i|d} f[d]$,可以用第二类莫比乌斯反演得到:
$f[i]=\sum \limits_{i|d} \mu (\frac{d}{i}) g[d]$
$f[1]$即为所求,所以每次更改选中状态时枚举更改的数的因子,更新它的$s[]$,进而更新它的$g[]$。
ans可以通过减去原来的g再加上新g得到。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=200005,M=500005; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } typedef long long ll; bool vis[M]; int pr[M],tot,mu[M]; void ini() { mu[1]=1; for(int i=2;i<=M-5;i++) { if(!vis[i])pr[++tot]=i,mu[i]=-1; for(int j=1;j<=tot&&i*pr[j]<=M-5;j++) { vis[i*pr[j]]=1; if(i%pr[j])mu[i*pr[j]]=-mu[i]; else { mu[i*pr[j]]=0; break; } } } } int n,m; int s[M],ctrl[N],a[N]; int main() { n=read();m=read(); ini(); for(int i=1;i<=n;i++) a[i]=read(); ll ans=0; while(m--) { int x=read(); for(int i=1;i*i<=a[x];i++) { if(a[x]%i==0) { if(!ctrl[x])ans+=1LL*mu[i]*s[i]; else ans-=1LL*(s[i]-1)*mu[i]; if(ctrl[x])s[i]--; else s[i]++; if(i*i==a[x])continue; if(!ctrl[x])ans+=1LL*mu[a[x]/i]*s[a[x]/i]; else ans-=1LL*(s[a[x]/i]-1)*mu[a[x]/i]; if(ctrl[x])s[a[x]/i]--; else s[a[x]/i]++; } } ctrl[x]^=1; printf("%lld\n",ans); } return 0; }
兴许青竹早凋,碧梧已僵,人事本难防。