- 上午
- 模拟考试,题太简单,老师连网都没断、、、
- Prob.1(AC)BFS,裸裸裸!
- Prob.2(AC)dp,刷表法比较方便
- Prob.3(RE2个点)一个费用流,要拆点。结果数组就开小了。某兔给spfa加了一个优先队列想要“优化”,结果还超时了两组。这东西有毒不能随便用啊。以后要优化的话,就最好用deque吧。
- BOZJ 1084 [SCOI2005]最大子矩阵
m==1和m==2两种情况分开做。
m==1 就不说了
m==2:
dp[i][j][l]表示第一列选到了i,第二列选到了j,且共选了l个子矩阵的最大值
三种转移:
1).枚举i向上的连续矩阵
2).枚举j向上的连续矩阵
3).如果i==j 枚举i,j以前向上的连续矩阵
代码:
#include<cstring> #include<iostream> using namespace std; int sum[105][3]; int n,m,x; void cmax(int &a,int b){ if(a<b) a=b; } void solve1(){ int dp[105][15]; memset(dp,0xc0,sizeof(dp)); dp[0][0]=0; for(int i=1;i<=n;i++) for(int l=0;l<=x;l++){ dp[i][l]=dp[i-1][l]; if(l==0) continue; for(int k=i;k>=1;k--) cmax(dp[i][l],dp[k-1][l-1]+sum[i][1]-sum[k-1][1]); } printf("%d",dp[n][x]); } void solve2(){ int dp[105][105][15]; memset(dp,0xc0,sizeof(dp)); dp[0][0][0]=0; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) if(i||j) for(int l=0;l<=x;l++){ dp[i][j][l]=max((i-1>=0?dp[i-1][j][l]:(int)0xc0c0c0c0),(j-1>=0?dp[i][j-1][l]:(int)0xc0c0c0c0)); if(l==0) continue; for(int k=i;k>=1;k--) cmax(dp[i][j][l],dp[k-1][j][l-1]+sum[i][1]-sum[k-1][1]); for(int k=j;k>=1;k--) cmax(dp[i][j][l],dp[i][k-1][l-1]+sum[j][2]-sum[k-1][2]); if(i!=j) continue; for(int k=i;k>=1;k--) cmax(dp[i][j][l],dp[k-1][k-1][l-1]+sum[i][1]-sum[k-1][1]+sum[j][2]-sum[k-1][2]); } printf("%d",dp[n][n][x]); } int main(){ scanf("%d%d%d",&n,&m,&x); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ scanf("%d",&sum[i][j]); sum[i][j]+=sum[i-1][j]; } if(m==1) solve1(); else solve2(); return 0; }
- 下午
- BOZJ 1085 [SCOI2005]骑士精神
IDA*
如果当期的棋盘与目标棋盘的差异大于剩下的操作数+1,就return 0;
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int mv[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}}; int aim[6][6]={{0,0,0,0,0,0},{0,1,1,1,1,1},{0,0,1,1,1,1},{0,0,0,2,1,1},{0,0,0,0,0,1},{0,0,0,0,0,0}}; int now[6][6],sx,sy; bool inmap(int x,int y){ return 1<=x&&x<=5&&1<=y&&y<=5; } int differ(){ int cnt=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) if(now[i][j]!=aim[i][j]) cnt++; return cnt; } bool dfs(int x,int y,int res){ if(res==0) return differ()==0; if(differ()-1>res) return 0; bool fg=0; for(int i=0;i<8&&!fg;i++){ int nx=x+mv[i][0]; int ny=y+mv[i][1]; if(!inmap(nx,ny)) continue; swap(now[x][y],now[nx][ny]); fg=dfs(nx,ny,res-1); swap(now[x][y],now[nx][ny]); } return fg; } int main(){ int T;scanf("%d",&T); char ch; bool fg; while(T--){ fg=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++){ scanf(" %c",&ch); if(ch=='0') now[i][j]=0; if(ch=='1') now[i][j]=1; if(ch=='*') now[i][j]=2,sx=i,sy=j; } for(int i=0;i<=15;i++) if(dfs(sx,sy,i)){ fg=1; printf("%d ",i); break; } if(!fg) printf("-1 "); } return 0; }
- BOZJ 1086 [SCOI2005]王室联邦
是一种树分块么,长知识了。
dfs时,维护一个栈,栈内维护访问过但还没确定所在省区的节点。
当枚举完当前节点的某些儿子后,发现没确定所在省区的节点个数已经大于B了,
就把他们划在一个省,弹出栈,省会城市为当前节点。
然后把当前节点入栈,并返回到当前节点的父亲。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; struct edge{ int to,next; }e[1005*2]; int head[1005],bel[1005],cap[1005],s[1005]; int n,B,ent=2,top,cnt; void add(int u,int v){ e[ent]=(edge){v,head[u]}; head[u]=ent++; } void dfs(int u,int fa){ int k=top; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(v==fa) continue; dfs(v,u); if(top-k>=B){ cap[++cnt]=u; while(top!=k) bel[s[top--]]=cnt; } } s[++top]=u; } int main(){ scanf("%d%d",&n,&B); for(int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs(1,0); while(top) bel[s[top--]]=cnt; printf("%d ",cnt); for(int i=1;i<=n;i++) printf("%d ",bel[i]); printf(" "); for(int i=1;i<=cnt;i++) printf("%d ",cap[i]); return 0; }
- BOZJ 1087 [SCOI2005]互不侵犯King
状压dp。
当时看到棋盘的点那么多,就没想状压,然后没想出来。
●状压不一定非要把所有信息都保存啊,就本题而言,完全可以值在dp状态中记录某一行的状态信息。
f[i][j][s]前i行中放了j个king,同时第i行的状态为s。
直接枚举会超时。
把所有合法状态先dfs枚举出来(没有相邻的1),只有不超过100种。
并预处理任意两个状态是否可以上下相邻。
然后就可以dp转移了。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define ll long long using namespace std; int sta[105],num[105]; ll f[15][105][105],ans; bool rela[105][105]; int n,m,cnt; void dfs(int p,int s,int l,int c){ if(p==n+1){ sta[++cnt]=s; num[cnt]=c; return; } dfs(p+1,s<<1,0,c); if(!l) dfs(p+1,s<<1|1,1,c+1); } void relation(){ for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++) rela[i][j]=!((sta[i]&sta[j])||((sta[i]<<1)&sta[j])||(sta[i]&(sta[j]<<1))); } void dp(){ f[0][0][1]=1; for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) for(int k=1;k<=cnt;k++){ if(!f[i][j][k]) continue; for(int l=1;l<=cnt;l++) if(rela[k][l]) f[i+1][j+num[l]][l]+=f[i][j][k]; } for(int i=1;i<=cnt;i++) ans+=f[n][m][i]; printf("%lld",ans); } int main(){ scanf("%d%d",&n,&m); dfs(1,0,0,0); relation(); dp(); return 0; }
- BOZJ 1088 [SCOI2005]扫雷Mine
列出式子,可以发现,确定了前两个位置后,后面的就可以确定了。
所以for循环判断是否合法就好了。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int num[10005],hve[10005]; int n,ans; bool judge(){ for(int i=3;i<=n;i++){ hve[i]=num[i-1]-hve[i-1]-hve[i-2]; if(hve[i]<0) return 0; } if(hve[n]+hve[n-1]!=num[n]) return 0; return 1; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&num[i]); if(num[1]==0) ans+=judge(); else if(num[1]==1){ hve[1]=1; ans+=judge(); memset(hve,0,sizeof(hve)); hve[2]=1; ans+=judge(); } else{ hve[1]=hve[2]=1; ans+=judge(); } printf("%d",ans); return 0; }
- 晚上
- BOZJ 1089 [SCOI2003]严格n元树
f[i] 深度<=i的树的个数
递推式: f[i]=f[i-1]^n+1
然后答案为 f[d]-f[d-1]
高精度搞搞。
这题数据范围,呃,最大数据太大了,跑不出来。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define bit 10000 using namespace std; struct Bigint{ int a[10000],len; Bigint(){ memset(a,0,sizeof(a)); len=0; } void operator =(int rtm){ Bigint now; if(!rtm) now.len=1; else while(rtm) now.a[++now.len]=rtm%bit,rtm/=bit; *this=now; } Bigint operator +(const Bigint &rtm) const{ Bigint now; now.len=max(len,rtm.len); for(int i=1;i<=now.len;i++){ now.a[i]+=a[i]+rtm.a[i]; now.a[i+1]+=now.a[i]/bit; now.a[i]%=bit; } while(now.a[now.len+1]) now.len++; return now; } Bigint operator +(const int &val) const{ Bigint now,rtm; rtm=val; now=(*this)+rtm; return now; } Bigint operator -(const Bigint &rtm) const{ Bigint now; now.len=max(len,rtm.len); for(int i=1;i<=now.len;i++){ now.a[i]+=a[i]-rtm.a[i]; if(now.a[i]<0) now.a[i]+=bit,now.a[i+1]--; } while(!now.a[now.len]) now.len--; return now; } Bigint operator -(const int &val) const{ Bigint now,rtm; rtm=val; now=(*this)-rtm; return now; } Bigint operator *(const Bigint &rtm) const{ Bigint now; now.len=len+rtm.len; for(int i=1;i<=len;i++) for(int j=1;j<=rtm.len;j++){ now.a[i+j-1]+=a[i]*rtm.a[j]; now.a[i+j]+=now.a[i+j-1]/bit; now.a[i+j-1]%=bit; } while(!now.a[now.len]) now.len--; return now; } Bigint operator *(const int &val) const{ Bigint now,rtm; rtm=val; now=(*this)*rtm; return now; } Bigint operator ^(int val) const{ Bigint now,bas; now=1; bas=(*this); while(val){ if(val&1) now=now*bas; bas=bas*bas; val>>=1; } return now; } void print(){ printf("%d",a[len]); for(int i=len-1;i>=1;i--) printf("%04d",a[i]); } }; int n,d; int main(){ Bigint a,b; a=0; b=1; scanf("%d%d",&n,&d); for(int i=1;i<=d;i++) a=b,b=(a^n)+1; b=b-a; b.print(); return 0; }
- BOZJ 1090 [SCOI2003]字符串折叠
区间dp,记忆化实现。
f[l][r]表示字符串l~r最小可以被压缩的长度。
转移:
1). 普通的拼接 f[l][r]=min(f[l][r],f[l][i]+f[i+1][r])
2). 如果区间重复 f[l][r]=min(f[l][r],f[l][i]+2+cal((r-l+1)/(i-l+1)))
上式的"2"是括号长度,cal是计算十进制数的字符长度。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; char s[105]; bool vis[105][105]; int f[105][105]; int cal(int x){ int cnt=0; while(x) cnt++,x/=10; return cnt; } bool repeat(int l,int r,int L,int R){ if((R-L+1)%(r-l+1)) return 0; for(int i=L,j=l;i<=R;i++,j++){ if(j>r) j=l; if(s[i]!=s[j]) return 0; } return 1; } int dp(int l,int r){ if(l==r) return 1; if(vis[l][r]) return f[l][r]; vis[l][r]=1; int res=r-l+1; for(int i=l;i<r;i++){ res=min(res,dp(l,i)+dp(i+1,r)); if(repeat(l,i,i+1,r)) res=min(res,dp(l,i)+2+cal((r-l+1)/(i-l+1))); } return f[l][r]=res; } int main(){ scanf("%s",s+1); int len=strlen(s+1); printf("%d",dp(1,len)); return 0; }