- 28上午
- 骚猪选讲
- 28下午
- BOZJ 1081 [SCOI2005]超级格雷码
感觉就是一个找规律,然后模拟输出。
半天没找到一个比较简便的模拟方法,
这份代码是学习网上一位大佬的,很巧妙。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int n,m,pow[40]; void print(int y){ if(y<10) printf("%d",y); else printf("%c",y-10+'A'); } int main(){ scanf("%d%d",&n,&m); pow[0]=1; for(int i=1;i<=n;i++) pow[i]=pow[i-1]*m; for(int i=0;i<pow[n];i++){ for(int j=n-1;j>=0;j--){ int x=i/pow[n-j]; int y=(i/pow[n-j-1])%m; if(x&1) print(m-y-1); else print(y); } printf(" "); } return 0; }
- BOZJ 1082 [SCOI2005]栅栏
二分+dfs_check(剪枝)。(感觉这个模型不容易看出来,太弱了555)
对木板和木材从小到大排序。
先二分答案mid,然后我们看看是否可以弄出前mid个小木板。
check时,用贪心策略:即尽量用小的木材去造大的木板。
所以从大到小枚举前mid个木板,然后从小到大枚举木材。
至于剪枝,就是记录浪费的木材,即某次裁剪后剩下的木材都不足以造最小的木板,就计入waste,
如果 waste+need(前mid个木板的总长)>tot(木材总长),就直接return 0;
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int A[55],totA,B[1005],sumB[1005],waste,need; int n,m; bool dfs(int p1,int p2){ if(p1==0) return 1; if(waste+need>totA) return 0; for(int i=p2;i<=m;i++){ if(A[i]>=B[p1]){ A[i]-=B[p1]; if(A[i]<B[1]) waste+=A[i]; bool fg=dfs(p1-1,B[p1]==B[p1-1]?p2:1); if(A[i]<B[1]) waste-=A[i]; A[i]+=B[p1]; if(fg) return 1; } } return 0; } int binary(int l,int r){ int mid,ans=0; while(l<=r){ mid=(l+r)>>1; need=sumB[mid]; if(dfs(mid,1)) ans=mid,l=mid+1; else r=mid-1; } return ans; } int main(){ scanf("%d",&m); for(int i=1;i<=m;i++) scanf("%d",&A[i]); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&B[i]); sort(A+1,A+m+1); sort(B+1,B+n+1); for(int i=1;i<=m;i++) totA+=A[i]; for(int i=1;i<=n;i++) sumB[i]=sumB[i-1]+B[i]; int ans=binary(0,n); printf("%d",ans); return 0; }
- BOZJ 1083 [SCOI2005]繁忙的都市
最小生成树kruskal。
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; struct edge{ int u,v,w; bool operator <(const edge &rtm) const{ return w<rtm.w; } }e[10005]; int fa[305]; int n,m,need; int find(int x){ return x==fa[x]?x:fa[x]=find(fa[x]); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); sort(e+1,e+m+1); need=n-1; for(int i=1;i<=m;i++){ int fu=find(e[i].u),fv=find(e[i].v); if(fu==fv) continue; fa[fv]=fu; need--; if(!need){ printf("%d %d",n-1,e[i].w); break; } } return 0; }
- 29下午+晚上
- 看了看bzoj的10月月赛题(和题解),后两道没看懂、、、
- BOZJ 5071 [Lydsy十月月赛]小A的数字
找到 A序列的变化规律,即每次操作使得A的前缀序列中的相邻两个元素交换位置。
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; ll sumA[100005],sumB[100005]; int main(){ int T,n; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",&sumA[i]),sumA[i]+=sumA[i-1]; for(int i=1;i<=n;i++) scanf("%lld",&sumB[i]),sumB[i]+=sumB[i-1]; sort(sumA+1,sumA+n+1); sort(sumB+1,sumB+n+1); for(int i=1;i<=n+1;i++){ if(i==n+1) printf("YES "); if(sumA[i]!=sumB[i]){ printf("NO "); break; } } } return 0; }
- BOZJ 5072 [Lydsy十月月赛]小A的树
很扯的树形dp。
看官方题解吧(感觉70%数据的题解有毒,怎么搞出的n^3?)
然后这份代码学习网上的一位大佬的。
在dp转移时,要多开两个数组防止用到本次转移的dp结果,
(当然也可以逆向枚举,但是因为for循环大了一点,会超时、、、)
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; struct edge{ int to,next; }e[5005*2]; int head[5005],color[5005]; int dmin[5005][5005],dmax[5005][5005],amin[5005],amax[5005],siz[5005],dn[5005],dx[5005]; int n,q,ent; void read(int &x){ static int f; static char ch; x=0; f=1; ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();} while('0'<=ch&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} x=x*f; } void add(int u,int v){ e[ent]=(edge){v,head[u]}; head[u]=ent++; } void dp(int u,int fa){ if(color[u]) dmax[u][1]=dmin[u][1]=1; else dmax[u][1]=dmin[u][1]=0; siz[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(v==fa) continue; dp(v,u); memcpy(dx,dmax[u],sizeof(dmax[u])),memcpy(dn,dmin[u],sizeof(dmin[u])); for(int j=1;j<=siz[u];j++) for(int k=1;k<=siz[v];k++){ dx[j+k]=max(dx[j+k],dmax[u][j]+dmax[v][k]); dn[j+k]=min(dn[j+k],dmin[u][j]+dmin[v][k]); } siz[u]+=siz[v]; for(int j=1;j<=siz[u];j++) dmax[u][j]=dx[j],dmin[u][j]=dn[j]; } for(int i=1;i<=siz[u];i++){ amax[i]=max(amax[i],dmax[u][i]); amin[i]=min(amin[i],dmin[u][i]); } } int main(){ int T; read(T); while(T--){ memset(dmin,0x3f,sizeof(dmin)); memset(amin,0x3f,sizeof(amin)); memset(dmax,0,sizeof(dmax)); memset(amax,0,sizeof(amax)); memset(head,0,sizeof(head)); ent=2; read(n); read(q); for(int i=1,u,v;i<n;i++){ read(u); read(v); add(u,v); add(v,u); } for(int i=1;i<=n;i++) read(color[i]); dp(1,0); for(int i=1,x,y;i<=q;i++){ scanf("%d%d",&x,&y); if(amin[x]<=y&&y<=amax[x]) puts("YES"); else puts("NO"); } printf(" "); } return 0; }
- BOZJ 5073 [Lydsy十月月赛]小A的咒语
dp[i][j] 表示到了A串的第i位,已经选了j段,构成B串的最大长度。
转移,刷表法:
1).A串的下一位不贡献,即不是构成B串的一份子。
dp[i+1][j]=max(dp[i+1][j],dp[i][j]);
2).A串的i位以后要贡献,即尝试去成为B串的一份子。
显然,按照贪心策略,此时选的要尽量长,
令l=lcp(A[i+1],B[dp[i][j]+1]),即A串后缀A[i+1~n-1]与B串后缀B[dp[i][j]+1~m-1]的最长公共前缀。
dp[i+l][j+1]=max(dp[i+l][j+1],dp[i][j]+l)
然后就是后缀数组+RMQ表维护lcp
网上还有一种不用后缀数组直接dp的方法,(但我没看懂)。
#include<cstdio> #include<cstring> #include<iostream> #define MAXN 100005 using namespace std; char s[MAXN*2]; int sa[MAXN*2],rk[MAXN*2],ht[MAXN*2]; int dp[MAXN][105],st[MAXN*2][20],log2[MAXN*2]; void print(int n){ puts("sa:"); for(int i=0;i<n;i++) printf("%d ",sa[i]); puts(""); puts("rk:"); for(int i=0;i<n;i++) printf("%d ",rk[i]); puts(""); puts("ht:"); for(int i=0;i<n;i++) printf("%d ",ht[i]); puts(""); } bool cmp(int *y,int len,int k,int p1,int p2){ int a=y[p1],b=y[p2]; int aa=p1+k<len?y[p1+k]:-1,bb=p2+k<len?y[p2+k]:-1; return a==b&&aa==bb; } void build(int n,int m){ static int wa[MAXN*2],wb[MAXN*2],c[MAXN*2],*x,*y,p; x=wa; y=wb; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[i]=s[i]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; for(int k=1;k<n;k<<=1){ p=0; for(int i=n-k;i<n;i++) y[p++]=i; for(int i=0;i<n;i++) if(sa[i]-k>=0) y[p++]=sa[i]-k; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[y[i]]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i]; m=1; swap(x,y); x[sa[0]]=0; for(int i=1;i<n;i++) x[sa[i]]=cmp(y,n,k,sa[i-1],sa[i])?m-1:m++; if(m>=n) break; } for(int i=0;i<n;i++) rk[sa[i]]=i; int h=0; for(int i=0,j;i<n;i++){ if(h) h--; if(rk[i]!=0){ j=sa[rk[i]-1]; while(i+h<n&&j+h<n&&s[i+h]==s[j+h]) h++; } ht[rk[i]]=h; } for(int i=1;i<n;i++) st[i][0]=ht[i]; for(int k=1;(1<<k)<n;k++) for(int i=1<<k;i<n;i++) st[i][k]=min(st[i-(1<<(k-1))][k-1],st[i][k-1]); } int lcp(int i,int j){ i=rk[i]; j=rk[j]; if(i>j) swap(i,j); i++; int k=log2[j-i+1]; return min(st[i+(1<<k)-1][k],st[j][k]); } int main(){ log2[1]=0; for(int i=2;i<200000;i++) log2[i]=log2[i>>1]+1; int T; scanf("%d",&T); while(T--){ int n,m,x; bool fg; memset(dp,0,sizeof(dp)); scanf("%d%d%d",&n,&m,&x); scanf("%s",s); s[n]='%'; scanf("%s",s+n+1); fg=0; build(n+m+1,301); for(int i=-1;i<n;i++) for(int j=0;j<=min(i+1,x);j++){ int d=i>=0?dp[i][j]:0; if(d==m) fg=1; dp[i+1][j]=max(dp[i+1][j],d); int l=(i+1<n&&n+1+d<n+m+1)?lcp(i+1,n+1+d):0; if(l==0) continue; dp[i+l][j+1]=max(dp[i+l][j+1],d+l); } if(fg) printf("YES "); else printf("NO "); } return 0; }
- BOZJ 5074 [Lydsy十月月赛]小B的数字
假设B序列为 b1,b2,b3.......
那同时也可以表示为 2^k1,2^k2,3^k3......
所以 x=2^(k1+k2+k3+......),令ak==k1+k2+k3+......
要使得每一个bi^ai(==2^(ki*ai))都能被x整除,
即需要满足(ki*ai)>=ak
所以 列出不等式:
k1*a1>=ak
k2*a2>=ak
k3*a3>=ak
.........
把左边的a移动到右侧
k1>=ak/a1
k2>=ak/a2
k3>=ak/a3
.........
再把所有式子相加得:
ak>=ak/a1+ak/a2+ak/a3+......
即 1>=1/a1+1/a2+1/a3+......
所以只要判断这个不等式是否满足就好了。
代码:
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const double eps=1e-8; int main(){ int T; scanf("%d",&T); while(T--){ double sum=0; int n; scanf("%d",&n); for(int i=1,a;i<=n;i++) scanf("%d",&a),sum+=1.0/a; if(sum<=1+eps) printf("YES "); else printf("NO "); } return 0; }