有更新 浙大月赛 ZOJ Monthly, June 2012 on June 24
zoj 3611
同类型的题目
化简模型即可得这样的问题,一幅图给定起点 终点 ,求起点到终点最多经过几个点,如果两种方案经过的点数相同,选路径总长短的
注意,这幅图最多只有12个点,所以立刻可以想到用状态压缩来做
dp[i][j]表示以i为终点的路径 为j状态(经过了那些点)时的最短路径
ps:在预处理最短路的时候不要将'$'算进去,就看成一个普通的可以走的格子就可以了
因为可能会出现如下情况,'$'的费用会算了四次,所以还是先数一下有几个'$'然后再独立算吧
#include<cstdio> #include<cstring> #include<map> #include<queue> #include<vector> #include<algorithm> using namespace std; typedef long long lld; int dir[4][2]={1,0,0,1,-1,0,0,-1}; const lld inf = 2139062143; char g[510][510]; int di[510][510]; bool vis[510][510]; map<int,pair<int,int> > mp; struct point{ int x,y; point(){} point(int a,int b):x(a),y(b){} }; int n,m; queue<point> pq; vector<pair<int,int> > edge[20]; point fr; void make(int dx,int dy){ point t; t.x=fr.x+dx; t.y=fr.y+dy; if(t.x>n || t.x<1 ||t.y<1 ||t.y>m || g[t.x][t.y]=='W' || g[t.x][t.y]=='#') return ; if(di[t.x][t.y]>di[fr.x][fr.y]+1){ di[t.x][t.y]=di[fr.x][fr.y]+1; if(!vis[t.x][t.y]){ pq.push(point(t.x,t.y)); vis[t.x][t.y]=true; } } } void delt(char op){ if(op=='U') make(-1,0); if(op=='D') make(1,0); if(op=='L') make(0,-1); if(op=='R') make(0,1); } void spfa(point src){ while(!pq.empty()) pq.pop(); memset(di,127,sizeof(di)); memset(vis,0,sizeof(vis)); vis[src.x][src.y]=1;di[src.x][src.y]=0;pq.push(src); while(!pq.empty()){ fr=pq.front();pq.pop(); vis[fr.x][fr.y]=false; if(g[fr.x][fr.y]=='0' || g[fr.x][fr.y]=='$'){ for(int i=0;i<4;i++){ make(dir[i][0],dir[i][1]); } } else { delt(g[fr.x][fr.y]); } } } int dp[13][(1<<13)]; bool v[20][(1<<13)]; void solve(int n,int s,int t){ memset(v,0,sizeof(v)); memset(dp,127,sizeof(dp)); queue<pair<int,int> > Q; Q.push(make_pair(s,(1<<s)));v[s][1<<s]=true; dp[s][1<<s]=0; while(!Q.empty()){ int fr=Q.front().first ; int st=Q.front ().second; Q.pop();v[fr][st]=false; int sz=edge[fr].size(); for(int i=0;i<sz;i++){ int node = edge[fr][i].first,w=edge[fr][i].second; if(dp[fr][st]+w<dp[node][st|(1<<node)]){ dp[node][st|(1<<node)]=dp[fr][st]+w; if(!v[node][st|(1<<node)]){ v[node][st|(1<<node)]=true; Q.push(make_pair(node,st|(1<<node))); } } } } int num=0,ans=inf; for(int i=0;i<(1<<n);i++)if(dp[t][i]!=inf){ int sum=0; for(int j=0;j<n;j++)if(i&(1<<j))sum++; if(sum>num){ num=sum; ans=dp[t][i]; } else if(sum==num){ if(dp[t][i]<ans)ans=dp[t][i]; } } num-=2; if(ans!=inf) printf("%d\n",ans+num*2); else printf("-1\n"); } int main(){ int i,j,k,x1,x2,y1,y2; while(scanf("%d%d",&n,&m)!=EOF){ mp.clear(); for(i=0;i<=13;i++) edge[i].clear(); k=0; for(i=1;i<=n;i++){ scanf("%s",g[i]+1); for(j=1;j<=m;j++){ if(g[i][j]=='$') mp[k++]=make_pair(i,j); } } scanf("%d%d%d%d",&x1,&y1,&x2,&y2); mp[k++]=make_pair(x1,y1);mp[k++]=make_pair(x2,y2);//start: k-2 , end : k-1 for(i=0;i<k;i++){ int x=(int)mp[i].first,y=(int)mp[i].second; point tmp=point(x,y); spfa(tmp); for(j=0;j<k;j++){ edge[i].push_back(make_pair(j,di[mp[j].first][mp[j].second])); } } solve(k,k-2,k-1); } }
zoj 3612
简单题,求第k大的数
#include<cstdio> #include<cstring> #include<map> #include<algorithm> using namespace std; typedef long long lld; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn = 10010; int sum[maxn<<2]; int num[maxn]; struct PP{ char s[10]; int v; }q[maxn]; void build(int l,int r,int rt){ sum[rt]=0; if(l==r) return ; int m=(l+r)>>1; build(lson); build(rson); } void update(int pos,int f,int l,int r,int rt){ sum[rt]+=f; if(l==r){ return ; } int m=(l+r)>>1; if(pos<=m) update(pos,f,lson); else update(pos,f,rson); } int query(int k,int l,int r,int rt){ if(l==r) { return l; } int m=(l+r)>>1; if(k<=sum[rt<<1]) return query(k,lson); else return query(k-sum[rt<<1],rson); } void print(int tot){ if(sum[1]%2){ int id=query(sum[1]/2+1,0,tot-1,1); printf("%d\n",num[id]); } else { int id1=query(sum[1]/2,0,tot-1,1); int id2=query(sum[1]/2+1,0,tot-1,1); if((num[id1]+num[id2])%2==0) printf("%lld\n",((lld)num[id1]+(lld)num[id2])/2); else printf("%.1lf\n",1.0*((lld)num[id1]+num[id2])/2); } } map<lld,int> mp; int main() { int t,n,i,j,k; scanf("%d",&t); while(t--) { mp.clear(); scanf("%d",&n); for(i=0;i<n;i++){ scanf("%s%d",q[i].s,&q[i].v); num[i]=q[i].v; } sort(num,num+n); int tot=unique(num,num+n)-num; build(0,tot-1,1); for(i=0;i<n;i++) { if(q[i].s[0]=='r'){ if(mp[q[i].v]){ mp[q[i].v]--; int pos=lower_bound(num,num+tot,q[i].v)-num; update(pos,-1,0,tot-1,1); if(sum[1]==0) puts("Empty!"); else { print(tot); } } else{ puts("Wrong!"); } } else { mp[q[i].v]++; int pos=lower_bound(num,num+tot,q[i].v)-num; update(pos,1,0,tot-1,1); print(tot); } } } return 0; }
zoj 3614
预处理两个sum
sum1[i][j]表示(1,1)到(i,j)的和。
sum2[i][j]表示(1,1) 到(i,j)的平方和。
算方差的时候将各项拆开,同类项合并,分别相加就好了
求子矩阵内的最大值,开始的时候用二维线段树写了,但是一直超时,经分析,原因如下:
复杂度为q*n*m*log(n)*log(m);
而RMQ预处理之后 是O(1)的回答,为 n*m*log^2+q*n*m少了点常数,所以用了RMQ之后立刻就过了
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long lld; inline int max(int a,int b){return a>b?a:b;} int sum1[310][310],g[310][310]; int f[310][310][9][9]; lld sum2[310][310]; int n,m; void init(){ int i,j; for(i=1;i<=n;i++) sum1[i][0]=0,sum2[0][i]=0; for(j=1;j<=m;j++) sum1[0][j]=0,sum2[0][j]=0; for(i=1;i<=n;i++)for(j=1;j<=m;j++){ sum1[i][j]=sum1[i-1][j]+sum1[i][j-1]+g[i][j]-sum1[i-1][j-1]; sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]+g[i][j]*g[i][j]-sum2[i-1][j-1]; } } int log2(int x) { int k=0; while((1<<(k+1))<x)++k; return k; } void ST() { int i,j,u,v,logn=log2(n),logm=log2(m); for(u=0;u<=logn;++u) for(v=0;v<=logm;++v) if(u+v)for(i=1;i+(1<<u)-1<=n;++i) for(j=1;j+(1<<v)-1<=m;++j) if(v==0)f[i][j][u][v]=max(f[i][j][u-1][v],f[i+(1<<(u-1))][j][u-1][v]); else f[i][j][u][v]=max(f[i][j][u][v-1],f[i][j+(1<<(v-1))][u][v-1]); } int get(int r1,int c1,int r2,int c2) { int k=log2(r2-r1+1),t=log2(c2-c1+1); int a=max(f[r1][c1][k][t],f[r1][c2-(1<<t)+1][k][t]); int b=max(f[r2-(1<<k)+1][c1][k][t],f[r2-(1<<k)+1][c2-(1<<t)+1][k][t]); return max(a,b); } int main(){ int i,j,k,cases=1; while(scanf("%d%d",&n,&m)!=EOF){ for(i=1;i<=n;i++)for(j=1;j<=m;j++){ scanf("%d",&g[i][j]); f[i][j][0][0]=g[i][j]; } ST(); init(); int q,x,y; printf("Case %d:\n",cases++); scanf("%d",&q); while(q--){ int ansx=-1,ansy=-1; double ret=100000000000.0; scanf("%d%d",&x,&y); for(j=1;j<=n-x+1;j++){ for(k=1;k<=m-y+1;k++){ lld sum=sum2[j+x-1][k+y-1]+sum2[j-1][k-1]-sum2[j-1][k+y-1]-sum2[j+x-1][k-1]; lld s=sum1[j+x-1][k+y-1]+sum1[j-1][k-1]-sum1[j-1][k+y-1]-sum1[j+x-1][k-1]; int mx=get(j,k,j+x-1,k+y-1); sum-=mx*mx; s-=mx; double aver=1.0*s/(x*y-1); double ans=sum+(x*y-1)*aver*aver-2.0*s*aver; ans/=(x*y-1); if(ans<ret){ ansx=j;ansy=k; ret=ans; } } } printf("(%d, %d), %.2lf\n",ansx,ansy,ret); } } return 0; }
zoj 3615 KMP + KM
建图利用 字符串的匹配KMP算法来搞
然后就是最优匹配KM的模板了
注:建图的时候可以把女孩到男孩的边也加进男孩到女孩的边权中,因为匹配了一对就表示双方的权值都会加上
由于很久没写这种模板,建图建错,WA了 7 次,囧。。。。
#include<cstdio> #include<cstring> const int inf = ~0u>>2; int n,m; int p[30]; char sent[210][10010],b[30]; char boy[210][25],girl[210][25]; void getp(char b[]){ p[1]=0; int i,j=0; for(i=2;i<=m;i++){ while(j>0&&b[j+1]!=b[i]) j=p[j]; if(b[j+1]==b[i]) j+=1; p[i]=j; } } int first; int kmp(char a[],char b[]){ int i,j=0,cnt=0; first=0; for(i=1;i<=n;i++){ while(j>0&&b[j+1]!=a[i]) j=p[j]; if(b[j+1]==a[i]) j+=1; if(j==m){ if(!first) first=i-m+1; cnt++; j=p[j]; } } return cnt; } int solve(int flag,int x,int y,int mm) { if(flag) { memset(p,0,sizeof(p)); n=strlen(sent[x]+1); m=strlen(girl[y]+1); getp(girl[y]); int num=kmp(sent[x],girl[y]); if(first) return first*num; } else { memset(p,0,sizeof(p)); n=strlen(sent[mm+x]+1); m=strlen(boy[y]+1); getp(boy[y]); int num=kmp(sent[mm+x],boy[y]); /* puts(boy[y]+1); printf("%d %d %d %d\n",flag,x,y,first*num); for(int i=1;i<=m;i++) printf("%d ",p[i]);puts("");*/ if(first) return first*num; } } inline int MIN(int a,int b){return a<b?a:b;} inline int max(int a,int b){return a>b?a:b;} const int INF = 100000000; #define MAX 250 int match[MAX]; bool sx[MAX],sy[MAX]; int lx[MAX],ly[MAX],map[MAX][MAX]; bool path(int u,int n) { sx[u]=true; for(int v=0;v<n;v++) if(!sy[v]&&lx[u]+ly[v]==map[u][v]) { sy[v]=true; if(match[v]==-1||path(match[v],n)) { match[v]=u; return true; } } return false; } int KM(int n) { int i,j; for(i=0;i<n;i++) { lx[i]=-INF; ly[i]=0; for(j=0;j<n;j++) if(lx[i]<map[i][j]) lx[i]=map[i][j]; } memset(match,-1,sizeof(match)); for(int u=0;u<n;u++) while(1) { memset(sx,0,sizeof(sx)); memset(sy,0,sizeof(sy)); if(path(u,n)) break; int dmin=INF; for(i=0;i<n;i++) if(sx[i]) for(j=0;j<n;j++) if(!sy[j]) dmin=MIN(lx[i]+ly[j]-map[i][j],dmin); for(i=0;i<n;i++) { if(sx[i]) lx[i]-=dmin; if(sy[i]) ly[i]+=dmin; } } int sum=0; for(j=0;j<n;j++) sum+=map[match[j]][j]; return sum; } int main() { int i,j,m,n; while(scanf("%d%d",&m,&n)!=EOF) { memset(map,0,sizeof(map)); for(i=0;i<m;i++) { scanf("%s",boy[i]+1); getchar(); int len=strlen(boy[i]+1); boy[i][len]=0; gets(sent[i]+1); } for(i=0;i<n;i++) { scanf("%s",girl[i]+1); getchar(); j=strlen(girl[i]+1); girl[i][j]=0; gets(sent[i+m]+1); } // for(i=0;i<m;i++) puts(boy[i]+1); // for(i=0;i<n;i++) puts(girl[i]+1); int ans=0,num; for(i=0;i<m;i++){ for(j=0;j<n;j++){ map[i][j]=solve(1,i,j,m); // printf("%d %d %d\n",i,j,map[i][j]); } } // puts(""); for(i=0;i<n;i++){ for(j=0;j<m;j++){ map[j][i]+=solve(0,i,j,m); // printf("%d %d %d\n",i,j,map[j][i]); } } ans=KM(max(m,n)); printf("%d\n",ans); } return 0; }
zoj 3616
主要要解决的问题:给你一个矩阵,有些位置的数可能为负,求一个和最大的子矩阵,要求这个子矩阵中不包含负数
注意题目给出的数据范围100行 2000列,行很少,所以可以考虑暴力枚举子矩阵的行数(起始行,和终止行),然后再从第一列开始枚举,直到某一列出现负数,就计算一下与出现负数的上一列之间的和,判断是否满足男女人数的要求,再用来更新答案即可,效率又排第一了,呵呵
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int l[2010],r[2010]; int sum[110][2010],num[110][2010]; int c1[110][2010],boy[110][2010],girl[110][2010]; int f[110][2010]; int n,m,b,g; void init(){ int i,j; for(i=1;i<=n;i++) sum[i][0]=0,c1[i][0]=0,boy[i][0]=0,girl[i][0]=0; for(j=1;j<=m;j++) sum[0][j]=0,c1[0][j]=0,boy[0][j]=0,girl[0][j]=0; for(i=1;i<=n;i++)for(j=1;j<=m;j++){ boy[i][j]=boy[i-1][j]+boy[i][j-1]+(!f[i][j])-boy[i-1][j-1]; girl[i][j]=girl[i-1][j]+girl[i][j-1]+(f[i][j])-girl[i-1][j-1]; c1[i][j]=c1[i-1][j]+c1[i][j-1]+(num[i][j]<0)-c1[i-1][j-1]; sum[i][j]=sum[i-1][j]+sum[i][j-1]+num[i][j]-sum[i-1][j-1]; } } bool judge(int s,int t,int col){//判断第col列,从s行到t行是否有负数 return c1[t][col]-c1[s-1][col]-c1[t][col-1]+c1[s-1][col-1]; } int calc(int s,int t,int pre,int now){//计算子矩阵的和 return sum[t][now]-sum[s-1][now]-sum[t][pre-1]+sum[s-1][pre-1]; } bool ok(int s,int t,int pre,int now){//是否至少有b个男孩,g个女孩 return (boy[t][now]-boy[s-1][now]-boy[t][pre-1]+boy[s-1][pre-1]>=b && girl[t][now]-girl[s-1][now]-girl[t][pre-1]+girl[s-1][pre-1]>=g); } int main(){ while(scanf("%d%d%d%d",&n,&m,&b,&g)!=EOF){ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d%d",&num[i][j],&f[i][j]);f[i][j]--; } } init(); int ans=0; for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ int pre=0; for(int k=1;k<=m;k++){ if(judge(i,j,k)){ if(k==1 || judge(i,j,k-1)) pre=k; else { int s=calc(i,j,pre+1,k-1); if(s>ans && ok(i,j,pre+1,k-1)) ans=s; pre=k; } } }if(!judge(i,j,m)){ int s=calc(i,j,pre+1,m); if(s>ans && ok(i,j,pre+1,m)) ans=s; } } } if(ans!=0) printf("%d\n",ans); else puts("No solution!"); } return 0; }
zoj 3617
贪心,假如在当前关卡冲能量(hp),当前每秒所能冲的hp值应该是局部最大,尽量让在当前关卡所充的能量能坚持到下一个比他大的关卡处
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int inf = ~0u>>2; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn = 100010; int Max[maxn<<2]; void pushup(int rt){ Max[rt]=max(Max[rt<<1],Max[rt<<1|1]); } void update(int p,int val,int l,int r,int rt){ if(l==r){ Max[rt]=val; return ; } int m=(l+r)>>1; if(p<=m) update(p,val,lson); else update(p,val,rson); pushup(rt); } int query(int L,int R,int val,int l,int r,int rt){ if(Max[rt] < val) return -1; if(l==r){ return l; } int m=(l+r)>>1; int ret=-1; if(L<=m) ret=query(L,R,val,lson); if(ret!=-1) return ret; if(R>m) ret=query(L,R,val,rson); return ret; } struct data{ int x,y; }in[maxn]; long long sum[maxn]; int main(){ int n,hp; while(scanf("%d%d",&n,&hp)!=EOF){ int mxhp=hp; sum[0]=0; for(int i=1;i<=n;i++){ scanf("%d%d",&in[i].x,&in[i].y); sum[i]=sum[i-1]+in[i].x; update(i,in[i].y,1,n,1); } int ans=0; for(int i=1;i<=n;i++){ hp-=in[i].x; int id=query(i+1,n,in[i].y,1,n,1); if(id==-1) id=n; if(sum[id]-sum[i]>=hp){ long long t=(sum[id]-sum[i]-hp)/in[i].y+1; if(t*in[i].y+hp<=mxhp){ ans+=t; hp+=t*in[i].y; } else { t=(mxhp-hp)/in[i].y; ans+=t; hp+=t*in[i].y; id=query(i+1,n,mxhp-hp,1,n,1); if(id==-1) id=n; if(hp<=sum[id]-sum[i]){ hp=mxhp; ans++; } } } } printf("%d\n",ans); } return 0; }
zoj 3618
字符串的哈希,字符串的哈希的题目做过好几次了
这道题目搞了半天,正着哈希反着哈希都无法有效的搞定,都无法转换成前缀的哈希
然后我发现我被坑了,题目告诉你那样子哈希,你不一定要那样子哈希啊。。。
基本思路是每到一个位置,判断一下是否存在以这个位置结尾的子串哈希值为N,当然这个哈希值还是题目的哈希值
这样的话就可以通过前缀的哈希值减去一个值(N转换过来的)变成前缀,再判断这个值被映射了几次
讲的很模糊
举个例子吧
BCBC b=2 p=1000000007 n=199=66*2+67
n为BC的哈希值,不难看出答案为2,即有两个子串哈希值为199
可以构造一个哈希的方法
sum[1]=66*2^3
sum[2]=66*2^3+67*2^2
sum[3]=66*2^3+67*2^2+66;
sum[4]=66*2^3+67*2^2+66*2+67
这样子就能保证减去n转换过来的一个值后,能转换成前缀的哈希,相当于 i 前缀减去一个以i结尾的子串还是一个前缀,只需要判断前缀被哈希了几次即可
注意这题还有个坑,字符可能为空格,所以要用gets(),代码超短,就不贴了,有需要可以向我要
zoj 3620
注意要建双向边,我Wa了一次
和第一题如出一辙(好奇怪干嘛出两个),稍微改了下条件,第一题还难一点呢
zoj 3621
首先要会求n为10进制时n!的末尾共有几个0,10 有2 5两个质因子,假设n!有x个2 y个5 所以n!末尾的0的个数就是min(x,y)
然后联想到末尾的0都是由进制数产生的,所以对进制数分解质因子,n!中哪个因子的个数最少就是答案
还要 注意比如18 进制 2 3 3 ,两个3一个2才能产生一个0
#include<cstdio> #include<cstring> #include<map> #include<queue> #include<algorithm> using namespace std; typedef long long lld; int num[100]; int tot; map<int,int> mp; void init(int k){ tot=0;mp.clear(); for(int i=2;;i++){ if(k%i==0){ num[++tot]=i; while(k%i==0){ mp[tot]++; k/=i; } if(k==1) return ; } } } void Min(lld &a,lld b){ if(a==-1||b<a) a=b; } int main(){ int i,j,k; char s[100]; while(scanf("%s%d",s,&k)!=EOF){ int len=strlen(s); lld sum=0; for(i=0;i<len;i++){ if(s[i]>='A' && s[i]<='Z'){ sum=sum*k+s[i]-'A'+10; } else if(s[i]>='a' && s[i]<='z'){ sum=sum*k+s[i]-'a'+36; } else sum=sum*k+s[i]-'0'; } init(k); lld ans=-1; for(i=1;i<=tot;i++){ lld cnt=0; lld tmp=sum; while(tmp){ tmp/=num[i]; cnt+=tmp; } cnt/=mp[i]; Min(ans,cnt); } printf("%lld\n",ans); } return 0; }