咂题
CODEVS1064虫食算
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
43#9865#045
+ 8468#6633
44445506978
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
BADC
+ CBDA
DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解,
思路:看到题后,就想到了按位搜索,从最低位到最高位,每次搜索都有三种情况(两加数都已有值、一个有、都没有),每种情况又有和有无值得两种情况,然后搜索就能过8个点(但是有一个小小的问题,要在输出时判断g是否为0,若不是的话就不满足了)。
加了一个剪枝:没一次进行递归之前都要将比i高的位数都判断一遍,一个judge解决,但是不能只考虑加数加加数等于和的情况,也要考虑加数加加数加1等于和的情况,毕竟会有进位。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; int ans[30]={0},a[30]={0},b[30]={0},c[30]={0},n; bool f[30]={false},ff=false; bool judge(int i) { int j; for (j=i+1;j<=n;++j) { if (ans[a[j]]!=-1&&ans[b[j]]!=-1&&ans[c[j]]!=-1) if ((ans[a[j]]+ans[b[j]])%n!=ans[c[j]]&&(ans[a[j]]+ans[b[j]]+1)%n!=ans[c[j]]) return true; } return false; } void work(int i,int g) { int j,k,x,y,z,he; if (i>n&&g==0) { for (j=1;j<=n-1;++j) cout<<ans[j]<<" "; cout<<ans[n]<<endl; ff=true; return; } x=a[i];y=b[i];z=c[i]; if (ans[x]!=-1&&ans[y]!=-1) { he=ans[x]+ans[y]+g; if (he%n==ans[z]&&ans[z]!=-1) work(i+1,he/n); if (ans[z]==-1) { if (!f[he%n]) { ans[z]=he%n; f[he%n]=true; if (!judge(i)) work(i+1,he/n); if (ff) return; ans[z]=-1; f[he%n]=false; } } } else { if (ans[x]==-1&&ans[y]==-1) { for (j=0;j<n;++j) { if (!f[j]) { for (k=0;k<n;++k) if (!f[k]) { he=j+k+g; if (ans[z]==-1) { if (!f[he%n]) { ans[z]=he%n; ans[x]=j; ans[y]=k; f[j]=true; f[k]=true; f[he%n]=true; if (!judge(i)) work(i+1,he/n); if (ff) return; ans[z]=-1; ans[x]=-1; ans[y]=-1; f[he%n]=false; f[j]=false; f[k]=false; } } else { if (he%n==ans[z]) { ans[x]=j; ans[y]=k; f[j]=true; f[k]=true; if (!judge(i)) work(i+1,he/n); if (ff) return; ans[x]=-1; ans[y]=-1; f[j]=false; f[k]=false; } } } } } } else { if ((ans[x]==-1&&ans[y]!=-1)||(ans[x]!=-1&&ans[y]==-1)) { if (ans[x]!=-1) { j=x;x=y;y=j; } for (j=0;j<n;++j) { if (!f[j]) { he=ans[y]+j+g; if (ans[z]!=-1) { if (he%n==ans[z]) { ans[x]=j; f[j]=true; if (!judge(i)) work(i+1,he/n); if (ff) return; f[j]=false; ans[x]=-1; } } else { if (!f[he%n]) { ans[z]=he%n; ans[x]=j; f[j]=true; f[he%n]=true; if (!judge(i)) work(i+1,he/n); if (ff) return; ans[z]=-1; ans[x]=-1; f[j]=false; f[he%n]=false; } } } } } } } if (ff) return; } int main() { int i,j; char ch; cin>>n; for (i=1;i<=n;++i) { cin>>ch; a[n-i+1]=ch-'A'+1; } for (i=1;i<=n;++i) { cin>>ch; b[n-i+1]=ch-'A'+1; } for (i=1;i<=n;++i) { cin>>ch; c[n-i+1]=ch-'A'+1; } for (i=1;i<=n;++i) ans[i]=-1; work(1,0); }
CODEVS1138聪明的质检员
题目描述 Description
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int v[200001]={0},w[200001]={0},li[200001]={0},ri[200001]={0}; long long sum[200001]={0},vv[200001]={0}; int main() { int i,j,n,m,mid,ll=0,rr=0; long long s=0,ans=0,ans1=0,ans2=0; scanf("%d%d%lld",&n,&m,&s); for (i=1;i<=n;++i) { scanf("%d%d",&w[i],&v[i]); if (w[i]>rr) rr=w[i]; sum[i]=sum[i-1]+1; vv[i]=vv[i-1]+v[i]; } for (i=1;i<=m;++i) { scanf("%d%d",&li[i],&ri[i]); --li[i]; ans1=ans1+(vv[ri[i]]-vv[li[i]])*(sum[ri[i]]-sum[li[i]]); } ++rr; ll=1; while (ll<rr-1) { mid=(ll+rr)/2; for (i=1;i<=n;++i) { sum[i]=sum[i-1]; if (w[i]>=mid) ++sum[i]; } for (i=1;i<=n;++i) { vv[i]=vv[i-1]; if (w[i]>=mid) vv[i]+=v[i]; } ans=0; for (i=1;i<=m;++i) ans+=(sum[ri[i]]-sum[li[i]])*(vv[ri[i]]-vv[li[i]]); if (ans>=s&&s>=ans2) { ll=mid; ans1=ans; } if (ans<=s&&s<=ans1) { rr=mid; ans2=ans; } } ans=min(abs(ans1-s),abs(ans2-s)); printf("%lld\n",ans); }
CODEVS1217借教室
在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要
向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份
订单,每份订单用三个正整数描述,分别为dj, sj, tj,表示某租借者需要从第sj天到第tj天租
借教室(包括第sj天和第tj天),每天需要租借dj个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提
供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教
室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申
请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改
订单。
思路:一开始想到了线段树,就写了,比较简单,在操作时,只用修改,如不符合就返回。但是这样的复杂度很高,会tle。后来从网上学习了可爱的二分+前缀和,十分简单的就AC了这道题。每次都要更新这个前缀和数组。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> using namespace std; struct use{ int left,right,mm; }a[5000000]; int ti[1000001]={0},delta[5000000]={0},dj,sj,tj,tot=0; bool ff=false; int build(int st,int en) { int mid,sum; ++tot;sum=tot; if (st==en) { a[sum].mm=ti[st]; return sum; } mid=(st+en)/2; a[sum].left=build(st,mid); a[sum].right=build(mid+1,en); a[sum].mm=min(a[a[sum].left].mm,a[a[sum].right].mm); return sum; } inline void paint(int x,int y) { a[x].mm+=y; if (a[x].mm<0) { ff=true; return; } delta[x]+=y; } inline void pushdown(int x) { paint(a[x].left,delta[x]); if (ff) return; paint(a[x].right,delta[x]); if (ff) return; delta[x]=0; } inline void work(int i,int st,int en) { int j,mid; if (sj<=st&&en<=tj) { paint(i,-dj); return; } pushdown(i); if (ff) return; mid=(st+en)/2; if (sj<=mid) work(a[i].left,st,mid); if (ff) return; if (tj>mid) work(a[i].right,mid+1,en); if (ff) return; a[i].mm=min(a[a[i].left].mm,a[a[i].right].mm); } int main() { freopen("classrooms.in","r",stdin); freopen("classrooms.out","w",stdout); int n,m,i,j,root; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%d",&ti[i]); root=build(1,n); for (i=1;i<=m;++i) { scanf("%d%d%d",&dj,&sj,&tj); work(1,1,n); if (ff) { printf("-1\n%d\n",i); break; } } if (!ff) printf("0\n"); fclose(stdin); fclose(stdout); }
#include<iostream> #include<cstdio> #include<cstring> using namespace std; long long a[1000001]={0},dj[1000001]={0},sj[1000001]={0},tj[1000001]={0}, ro[1000001]={0}; int n,m; bool judge(int mid) { int i,j; long long sum=0; memset(a,0,sizeof(a)); for (i=1;i<=mid;++i) { a[sj[i]]+=dj[i]; a[tj[i]+1]-=dj[i]; } for (i=1;i<=n;++i) { sum+=a[i]; if (sum>ro[i]) return false; } return true; } int main() { int i,j,l,r,ans=0,mid; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%d",&ro[i]); for (i=1;i<=m;++i) scanf("%d%d%d",&dj[i],&sj[i],&tj[i]); l=1;r=m; while (l<=r) { mid=(l+r)/2; if (!judge(mid)) { r=mid-1; ans=mid; } else { l=mid+1; } } if (ans==0) printf("0\n"); else printf("-1\n%d\n",ans); }
CODEVS1029 遍历问题
我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:
所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int main() { char ch1[30],ch2[30]; int l,i,j,c=0,ans; cin>>ch1>>ch2; l=strlen(ch1); for (i=0;i<l-1;++i) for (j=l-1;j>=1;--j) if (ch1[i]==ch2[j]&&ch1[i+1]==ch2[j-1]) ++c; ans=1; for (i=1;i<=c;++i) ans*=2; cout<<ans<<endl; }
CODEVS2292
游戏规则如下:
在一条长长的纸上有N个格子,每个格子上都有一个数,第i格的数记为Ai,机器头刚开始在第1格。这个游戏有两个操作:
1.如果现在在第i格,则可以移动机器头到第Ai格;
2.把某个Ai减少或增加1。
然而,fotile96看了之后却不以为然。“嗯,你挑战一下用最少次数使机器头到达第N格吧,这样好玩些……”
现在,Shadow已经快Crazy了。于是,Shadow把脸转向了你……
思路:进行广搜最短路,每次有三种状态:进行已知的位移,位置加一或减一。比较简单的广搜,但是一开始无奈的写错了程序,理解错了题意,1这个点第一次只能到a[1]的位置,不能+1或-1,一开始就是因为没有理解好题,写出了tle一个点的东西。用一个数组标记这个点是否入队,只标记,不置反。这样能保证一个点的ans最优,同时避免了循环队列。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int que[5000001][3]={0},a[100001]={0},n,ll,ci[100001]={0}; void bfs() { int head,tail,tt,i; head=0;tail=1; que[1][0]=1; que[1][1]=a[1]; que[1][2]=0; ci[1]=0; while (head!=tail) { head=head%ll+1; if (que[head][0]!=que[head][1]) { tt=tail%ll+1; if (que[head][2]+1<ci[que[head][1]]) { ci[que[head][1]]=que[head][2]+1; tail=tail%ll+1; que[tail][0]=que[head][1]; que[tail][1]=a[que[tail][0]]; que[tail][2]=que[head][2]+1; } if (que[tail][0]==n) break; } if (que[head][1]<n&&que[head][2]+2<ci[que[head][1]+1]) { ci[que[head][1]]=que[head][2]+2; tail=tail%ll+1; que[tail][0]=que[head][0]; que[tail][1]=que[head][1]+1; que[tail][2]=que[head][2]+1; } if (que[head][1]>1&&que[head][2]+2<ci[que[head][1]-1]) { ci[que[head][1]]=que[head][2]+2; tail=tail%ll+1; que[tail][0]=que[head][0]; que[tail][1]=que[head][1]-1; que[tail][2]=que[head][2]+1; } } } int main() { int i,j; memset(ci,127,sizeof(ci)); ll=5000000; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); bfs(); printf("%d\n",ci[n]); }
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int que[1000001]={0},a[100001]={0},n,ll,ci[100001]={0}; bool use[100001]={false}; void bfs() { int head,tail,i; head=tail=1; que[1]=a[1]; ci[a[1]]=1; use[a[1]]=true; while (head<=tail) { if (que[head]==n) break; if (!use[a[que[head]]]) { tail=tail+1; que[tail]=a[que[head]]; ci[que[tail]]=ci[que[head]]+1; use[que[tail]]=true; } if (que[head]<n&&!use[que[head]+1]) { tail=tail+1; que[tail]=que[head]+1; ci[que[tail]]=ci[que[head]]+1; use[que[tail]]=true; } if (que[head]>1&&!use[que[head]-1]) { tail=tail+1; que[tail]=que[head]-1; ci[que[tail]]=ci[que[head]]+1; use[que[tail]]=true; } ++head; } } int main() { int i,j; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&a[i]); bfs(); if (n==1) printf("0\n"); else printf("%d\n",ci[n]); }
破译密码
题目大意:对字符串t,对f(t)=sigma(k=0~len-1)26^(n-k-1)*num(tk) mod p(num()表示这个字母在字母表中的顺序,‘a'为0)。同时定义t[k]为向左旋转k次后的字符串。给定h数组,求一个字符串满足f(t[k])=h(k)。
思路:我们把每一个h对应的f写出来后,发现26hi-h(i+1)=(26^n-1)tk,所以我们求出(26^n-1)的逆元,然后扫一遍求出来就可以了。但是可能会出现这个式子没有逆元(这个式子为0),那么我们会发现只要满足其中一个hi,就会满足所有的,所以我们可以选任意一个hi转为26进制数,然后相应的位置上填相应的字母就可以了,这个的证明如下:h(i+1)=26hi(【-(26^n-1)xi】=0)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define LL long long using namespace std; LL hi[maxnode]={0},p,ans[maxnode]={0}; LL mi(LL x,LL y) { if (y==1) return x%p; LL m=mi(x,y/2); if (y%2) return (m*m%p)*x%p; else return m*m%p; } int main() { freopen("secret.in","r",stdin); freopen("secret.out","w",stdout); int i,j,n;LL m; scanf("%d%I64d",&n,&p); for (i=1;i<=n;++i) scanf("%I64d",&hi[i]); if ((m=mi(26,n))==1) { i=1; while(hi[1]) { ans[n-i+1]=hi[1]%26;hi[1]/=26;++i; } for (i=1;i<=n;++i) printf("%c",(char)(ans[i]+'a')); } else { m=mi(m-1,p-2); for (i=1;i<=n;++i) printf("%c",(char)(((26LL*hi[i]%p-hi[(i==n?1:i+1)])%p*m%p+p)%p+'a')); } }
bzoj1258 三角形tri
题目大意:如图命名三角形,求一个三角形相邻的边长比它长或者相等的三角形的编号。
思路:找规律后,发现可以采用模拟人工的方法。如果最后一位是4,那么相邻的就是最后一位改为1、2、3的三角形。如果不是这样,从后往前看的话,到第i位时,i位之前的数字是没有影响的;最后一位改成4的一定是,然后从倒数第二位倒着(到0)扫一遍,如果后面没有出现过i+1位的数字,那么就可以和前i位同给定三角形,第i+1位是4的三角形相邻。(这样还保证了字典序升序)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 55 using namespace std; char ss[maxnode]; int ans[10]={0}; bool use[10]={0}; int main() { int n,i,j; scanf("%s",&ss);n=strlen(ss)-1; if (ss[n]=='4') { for (i=1;i<=3;++i) { printf("T"); for (j=1;j<n;++j) printf("%c",ss[j]); printf("%d\n",i); } } else { ans[++ans[0]]=n-1; for (i=n-2;i>=0;--i) { use[ss[i+2]-'0']=true; if (ss[i+1]!=ss[i+2]&&!use[ss[i+1]-'0']) ans[++ans[0]]=i; } for (i=1;i<=ans[0];++i) { printf("T"); for (j=1;j<=ans[i];++j) printf("%c",ss[j]); printf("%d\n",4); } } }
bzoj1088 扫雷
题目大意:一个n×2的扫雷,只有第一列有雷,给定第二列的数字,求第一列的方案数。
思路:我们知道第一列第一个是什么之后,后面的就可以递推了。所以我们假设第一个是0/1,分别求一下,看是否能有合法解。判断能否有合法解首先要看中途有无那个格子是负数,同时还要看最后一个格子是否符合。
#include<iostream> #include<cstdio> #define maxnode 10005 using namespace std; int ai[maxnode]={0},bi[maxnode]={0},ci[maxnode]={0},n; int calc(int k) { int i,j; for (i=1;i<=n;++i) ci[i]=ai[i]; bi[1]=k;ci[1]-=k;ci[2]-=k; for (i=2;i<=n;++i) { bi[i]=ci[i-1];ci[i]-=bi[i]; ci[i+1]-=bi[i];if (bi[i]<0||ci[i]<0) return 0; } if (ci[n]>0) return 0; return 1; } int main() { int i,j,k,ans=0; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%d",&ai[i]); for (k=0;k<=1;++k) ans+=calc(k); printf("%d\n",ans); }
bzoj1011 遥远的行星
题目大意:给定n颗行星和一个0~0.35间的a,i<=a*j时,i对j的作用力是mi*mj/(j-i),求出每个星球的受力情况。(答案误差不超过5%都算对)(!!!)
思路:因为答案允许的误差非常大,所以我们可以暴力算出前10000的答案,后面的可以对分母取中位数,答案误差会比较小。但也要注意精度问题(!!!)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define maxnode 100005 using namespace std; double mi[maxnode]={0},ans[maxnode]={0},sum[maxnode]={0}; int main() { int n,i,j,t;double a; scanf("%d%lf",&n,&a); for (i=1;i<=n;++i) { scanf("%lf",&mi[i]);sum[i]=sum[i-1]+mi[i]; } for (i=1;i<=min(10000,n);++i) { j=(int)floor((double)i*a+1e-6); for (t=1;t<=j;++t) ans[i]+=mi[t]*mi[i]/((double)(i-t)*1.0); } for (i=10001;i<=n;++i) { j=(int)floor((double)i*a+1e-6); ans[i]=mi[i]*sum[j]/((double)(i-(int)(j/2))*1.0); } for (i=1;i<=n;++i) printf("%.6f\n",ans[i]); }
bzoj1213 高精度开根
题目大意:求开a次根号下的b的整数部分
思路:二分+python。二分中有一些技巧避免错误,同时在做之前要把l、r缩小范围,也是一个技巧。
a=int(input()) b=int(input()) l=0 r=1 while r**a<=b: l=r r=r*2 while l<r: mid=(l+r)//2 if (mid**a<=b): l=mid+1 ans=mid else: r=mid print(ans)
bzoj3170 松鼠聚会
题目大意:给定n个点,一个点和它周围8个点的距离为1,求所有点到某一点的最小距离和。
思路:这个距离的定义就相当于max(|x2-x1|,|y2-y1|),这个叫做切比雪夫距离。这种距离和曼哈度距离之间可以相互转化,把原来点的坐标改为(x+y,x-y),那么原来的切比雪夫距离就变成了曼哈顿距离*2,这里我们枚举每一个点,计算其他点到它的距离,用前缀和搞一下就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define LL long long using namespace std; struct use{ LL xi,yi;int po; }ai[maxnode]={0},xx[maxnode]={0},yy[maxnode]={0}; LL sum[maxnode][2]={0},ans[maxnode]={0}; int cmp(const use &x,const use &y){return x.xi<y.xi;} int main() { int n,i,j;LL x,y,ansi; scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%I64d%I64d",&x,&y); xx[i].xi=x+y;yy[i].xi=x-y; xx[i].po=yy[i].po=i; }sort(xx+1,xx+n+1,cmp); sort(yy+1,yy+n+1,cmp); for (i=2;i<=n;++i){ xx[i].xi-=xx[1].xi;yy[i].xi-=yy[1].xi; }xx[1].xi=yy[1].xi=0; for (i=1;i<=n;++i){ ai[xx[i].po].xi=i;ai[yy[i].po].yi=i; sum[i][0]=sum[i-1][0]+xx[i].xi; sum[i][1]=sum[i-1][1]+yy[i].xi; } for (i=1;i<=n;++i){ ans[xx[i].po]+=(LL)(i-1)*xx[i].xi-sum[i-1][0]+ sum[n][0]-sum[i][0]-(n-i)*xx[i].xi; ans[yy[i].po]+=(LL)(i-1)*yy[i].xi-sum[i-1][1]+ sum[n][1]-sum[i][1]-(n-i)*yy[i].xi; }ansi=ans[1]; for (i=2;i<=n;++i) ansi=min(ansi,ans[i]); printf("%I64d\n",ansi/2); }
bzoj3210 花神的浇花集会
题目大意:给定一些点,求一个点使得切比雪夫距离和最小。
思路:转化成曼哈顿距离后,取中位数点,如果这个点横纵坐标之和为奇数(在原来的点中不存在非整点,所以不合适),要微调(上下左右)一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define LL long long using namespace std; struct use{ LL xi,yi;int po; }ai[maxnode]={0},xx[maxnode]={0},yy[maxnode]={0}; int cmp(const use&x,const use&y){return x.xi<y.xi;} LL dx[4]={0LL,1LL,0LL,-1LL},dy[4]={1LL,0LL,-1LL,0LL}; LL ab(LL x){return (x<0 ? -x : x);} int main() { int n,i,j;LL x,y,zx,zy,ans=0,cans;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%I64d%I64d",&x,&y); xx[i].xi=x+y;yy[i].xi=x-y;xx[i].po=yy[i].po=i; }sort(xx+1,xx+n+1,cmp); sort(yy+1,yy+n+1,cmp); zx=xx[n/2+1].xi;zy=yy[n/2+1].xi; if ((zx+zy)%2){ ans=0x7fffffffffffffffLL; for (i=0;i<4;++i){ cans=0;x=zx+dx[i];y=zy+dy[i]; for (j=1;j<=n;++j) cans+=ab(x-xx[j].xi)+ab(y-yy[j].xi); ans=min(ans,cans); } } else for (i=1;i<=n;++i) ans+=ab(zx-xx[i].xi)+ab(zy-yy[i].xi); printf("%I64d\n",ans/2); }
bzoj1149 风玲
题目大意:判断一棵树能否通过交换左右子树变为完全二叉树,及最小交换次数。
思路:我们很容易判断出一种不成立的情况,就是最深的叶子节点和最浅的叶子节点深度差大于1。判掉这种情况,我们可以dfs,看这两颗子树的情况(有无最大最小深度的叶子节点),又可以判出一种不成立:就是左右孩子均同时有最深最浅叶子节点,其他的通过一些判断给答案加上去就可以了。注意更新父亲节点的最深最浅节点信息。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 using namespace std; struct use{ int li,ri; }dian[maxnode]={0}; int fi[maxnode]={0},dep[maxnode]={0},md[maxnode]={0},ans=0,minn,maxn=0; void work(int u) { int ls,rs,i,j;ls=dian[u].li;rs=dian[u].ri; if (ls>0) work(ls);if (rs>0) work(rs); if (ans==-1) return; if (ls<0&&rs<0){md[u]=(dep[u]==minn?0:1);return;} if (ls<0){md[u]=2;++ans;return;} if (rs<0){md[u]=2;return;} if (md[ls]==2&&md[rs]==2){ans=-1;return;} if (md[ls]==md[rs]){md[u]=md[ls];return;} md[u]=2; if (md[ls]==2){if (md[rs]==1) ++ans;return;} if (md[rs]==2){if (md[ls]==0) ++ans; return;} if (md[ls]<md[rs]) ++ans; } int main() { int n,m,i,ye;scanf("%d",&n); minn=2100000000LL;ye=n;dep[1]=1; for (i=1;i<=n;++i){ scanf("%d%d",&dian[i].li,&dian[i].ri); dep[dian[i].li]=dep[dian[i].ri]=dep[i]+1; if (dian[i].li<0||dian[i].ri<0){maxn=max(maxn,dep[i]);minn=min(minn,dep[i]);} } if (maxn-minn>1) printf("-1\n"); else{work(1);printf("%d\n",ans);} }
bzoj1064 假面舞会
题目大意:给定n个人和m种关系,关系为a能看到b,其中每个人有一个分类,第i类人能看到i+1(k看到1),根据已知,求最大类数和最小的。
思路:对于一个有向图,主要有链(含交叉的情况)、环、两点间的两条路径这三种情况,对于第一种就dfs统计就可以了;对于后两种,可以一起处理,把边建成双向的,反向边的权值为-1,正向为1,当环的时候,如果这个点之前访问过了,就用当前的-之前的(注意如果这个环的大小为0,说明有重边,要舍弃)。最后如果有环,就取一下环大小的gcd为最大值,这个gcd的最小的大于2的因数为最小值;如果没环,最大值就是所有链长之和,最小值就是3,判断一下无解就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2000005 #define inf 2100000000 using namespace std; int point[maxm]={0},en[maxm]={0},next[maxm]={0},va[maxm]={0},dep[maxm]={0}, huan[maxm]={0},ru[maxm]={0},maxn,minn,tot=0; bool visit[maxm]={false}; int ab(int x){return (x<0?-x:x);} int gcd(int a,int b){return (b==0?a:gcd(b,a%b));} void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=1; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=-1; } void dfs1(int u,int fa){ int v,i,j;visit[u]=true; for (i=point[u];i;i=next[i]){ if ((v=en[i])==fa) continue; if (visit[v]){huan[++huan[0]]=ab(dep[u]+va[i]-dep[v]);if (!huan[huan[0]])--huan[0];} else{dep[v]=dep[u]+va[i];dfs1(v,u);} } } void dfs2(int u,int fa){ int v,i,j;visit[u]=true; maxn=max(maxn,dep[u]);minn=min(minn,dep[u]); for (i=point[u];i;i=next[i]){ if ((v=en[i])==fa||visit[v]) continue; dep[v]=dep[u]+va[i];dfs2(v,u); } } int main(){ int n,m,i,j,u,v,sum=0;scanf("%d%d",&n,&m); for (i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v);++ru[v];} for (i=1;i<=n;++i)if (!visit[i]){dep[i]=1;dfs1(i,0);} memset(visit,false,sizeof(visit)); for (i=1;i<=n;++i)if (!visit[i]){dep[i]=1;minn=inf;maxn=0;dfs2(i,0);sum+=maxn-minn+1;} if (!huan[0]){ if (sum<3) printf("-1 -1\n"); else printf("%d %d\n",sum,3); }else{ sum=huan[1]; for (i=2;i<=huan[0];++i) sum=gcd(sum,huan[i]); if (sum<3) printf("%d %d\n",-1,-1); else{ for (i=3;i<=sum;++i){if (sum%i==0) break;} printf("%d %d\n",sum,i); } } }
bzoj1082 栅栏
题目大意:给定n块木板,要求切成给定长度的m块木块,求最多能切多少块。
思路:二分答案+dfs判断(神剪枝!!!)。一定是用小的木板切尽量大的木块使答案更优,有两个优化:1.如果这个木块和上一个木块一样大,就从当前用到的大木板开始判断;2.如果当前已经浪费的木板长度(如果一块木板剩下的长度比最小的木块都小了,就认为是浪费的)比mid之后所有木块之和都大了,就认为不合理。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int a[100]={0},c[100]={0},n,m,ans=0,wu[1500]={0},sum[1500]={0},waste,mid,tot=0; bool dfs(int x,int y){ int i,j;if (!x) return true; if (waste>tot-sum[m-mid]) return false; for (i=y;i<=n;++i) if (c[i]>=wu[x]){ c[i]-=wu[x]; if (c[i]<wu[1]) waste+=c[i]; if (wu[x-1]==wu[x]){if(dfs(x-1,i)) return true;} else{if (dfs(x-1,1)) return true;} if (c[i]<wu[1]) waste-=c[i]; c[i]+=wu[x]; } return false; } int main() { int i,l,r,ans;scanf("%d",&n); for (i=1;i<=n;++i){scanf("%d",&a[i]);tot+=a[i];} sort(a+1,a+n+1);scanf("%d",&m); for (i=1;i<=m;++i) scanf("%d",&wu[i]); sort(wu+1,wu+m+1);l=0;r=m; for (i=1;i<=m;++i) sum[i]=sum[i-1]+wu[i]; while(l<r){ mid=(l+r)/2;waste=0; for (i=1;i<=n;++i) c[i]=a[i]; if (dfs(m-mid,1))r=mid; else l=mid+1; }printf("%d\n",m-l); }
bzoj1816 扑克牌
题目大意:给定每种牌的个数,j牌(m个)能代表所有牌,要求一套牌中每种牌只能有一张(包括j),求最多能凑出的套数。
思路:二分答案+贪心。二分之后,m要和mid取min(因为j牌最多只能用mid张),扫一遍,如果不到mid就在总的m中减去不到的,如果中途小于0了就return false。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define maxm 100 using namespace std; int ci[maxm]={0},m,n; bool judge(int x){ int i,j;j=min(x,m); for (i=1;i<=n;++i){ if (ci[i]<x) j=j-x+ci[i]; if (j<0) return false; }return true; } int main(){ int i,ans,l,r,mid;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) scanf("%d",&ci[i]); l=1;r=2100000000; while(l<=r){ mid=(l+r)/2; if (judge(mid)){ans=mid;l=mid+1;} else r=mid-1; }printf("%d\n",ans); }
bzoj1085 骑士精神
题目大意:给定一个5*5的方格,里面有12个0、12个1、1个空位,数字可以按马的走法到空位,求到达指定终局的最少步数(超过15步为无解)。
思路:数据范围很小,所以可以暴搜,按空位的位置搜索。当然要有一个剪枝:如果当前局面和终局不同的个数加上已走步数超过了最优解,则不需要搜索。还有一个技巧,因为只有15步,所以我们可以从小到大给搜索加一个上界看能否搜出答案(这样会快一半)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define n 5 #define up 15 using namespace std; int gi[5][5]={{1,1,1,1,1},{0,1,1,1,1},{0,0,-1,1,1},{0,0,0,0,1},{0,0,0,0,0}}; int map[5][5]={0},dx[8]={1,2,2,1,-1,-2,-2,-1},dy[8]={2,1,-1,-2,2,1,-1,-2},ans; bool ff; bool judge(){ int i,j; for (i=0;i<n;++i) for (j=0;j<n;++j) if (map[i][j]!=gi[i][j]) return false; return true; } bool can(int d){ int i,j; for (i=0;i<n;++i) for (j=0;j<n;++j) if (map[i][j]!=gi[i][j]) if (++d>ans) return false; return true; } void dfs(int x,int y,int d){ int i,j,xx,yy; if (d>=ans) return; if (judge()){ans=d;return;} for (i=0;i<8;++i){ xx=x+dx[i];yy=y+dy[i]; if (xx<0||xx>=n||yy<0||yy>=n) continue; swap(map[x][y],map[xx][yy]); if (can(d)) dfs(xx,yy,d+1);if (ff) return; swap(map[x][y],map[xx][yy]); } } int main(){ int t,i,j,x,y;char ch;scanf("%d",&t); while(t--){ for (i=0;i<n;++i){ for (j=0;j<n;++j){ while(scanf("%c",&ch)==1) if (ch=='0'||ch=='1'||ch=='*') break; map[i][j]=(ch=='*'?-1:ch-'0'); if (ch=='*'){x=i;y=j;} } }ans=up+1;dfs(x,y,0); printf("%d\n",ans<=up?ans:-1); } }
bzoj2144 跳跳棋(!!!)
题目大意:棋子可以以一个棋子为中轴跳跃,但是不能跨过两个棋子,给定三颗棋子的起态和终态,判断能否达成以及最少步数。
思路:考虑x、y、z的三颗棋子,他们跳跃的状态就是左到中、右到中、中到左或右,于是就成了一个二叉树,问题就成了求解二叉树上两点的距离。设d1=y-x,d2=z-y。如果d1<d2,那么只能跳x、y,跳(d2-1)/d1步;d2>d1同理,这样就很像辗转相除了,logn的复杂度,因为我们不可能建出这棵树,所以我们应该二分判断一下,每次往上跳(不过首先用到lca的思想,先调整到同一高度)。
非常神的思路,orzsunshine!!!
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define up 1000000000 #define n 3 using namespace std; struct use{ int num[4]; }ai,bi,at,bt; int ad,bd,dd=0; bool judge(use x,use y){ for (int i=1;i<=n;++i) if (x.num[i]!=y.num[i]) return false; return true; } use work(use x,int dep){ int d1,d2,d;d1=x.num[2]-x.num[1];d2=x.num[3]-x.num[2]; if (!dep||d1==d2) return x; if (d1<d2){ d=min(dep,(d2-1)/d1);dd+=d;dep-=d; x.num[1]+=d*d1;x.num[2]+=d*d1; }else{ d=min(dep,(d1-1)/d2);dd+=d;dep-=d; x.num[2]-=d*d2;x.num[3]-=d*d2; }return work(x,dep); } int main(){ int i,j,l,r,mid; for (i=1;i<=n;++i) scanf("%d",&ai.num[i]); sort(ai.num+1,ai.num+n+1); for (i=1;i<=n;++i) scanf("%d",&bi.num[i]); sort(bi.num+1,bi.num+n+1); at=work(ai,up);ad=dd;dd=0;bt=work(bi,up);bd=dd; if (!judge(at,bt)) printf("NO\n"); else{ if (bd>ad){swap(ai,bi);swap(ad,bd);} ai=work(ai,ad-bd);l=0;r=bd; while(l!=r){ mid=(l+r)/2; if (!judge(work(ai,mid),work(bi,mid))) l=mid+1; else r=mid; }printf("YES\n%d\n",ad-bd+l*2); } }
bzoj4292 Równanie
题目大意:f[n]表示n十进制下各位数平方和,给定k、a、b,求a<=n<=b中k*f[n]==n的n的个数。
思路:穷举f[n],然后暴力判断就行了。这里穷举的范围是从(a/k)(上取整)~min(b/k(下取整),1458)(因为18位数都是9的话最多也只有这么多)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; LL k; bool judge(LL x,LL i){ LL sum=0,y; while(x){y=x%10;sum+=y*y;x/=10;} return sum==i; } int main(){ LL a,b,up,i;int ans=0;scanf("%I64d%I64d%I64d",&k,&a,&b); up=min(1500LL,b/k); for (i=(a+k-1)/k;i<=up;++i) if (judge(i*k,i)) ++ans; printf("%d\n",ans); }
bzoj4029 定价
题目大意:一个数的不合理度是去掉末尾0后长度×2,如果末尾是5就-1。求[l,r]中不合理度最小的最小正整数。
思路:贪心取数就可以了。如果这两个数前面的某些位一样,就先把这些去掉不考虑。剩下的就尽量先取5的,再取其他的尽量小的,但是如果之前有去掉的部分,就要让这些数尽量大(这样的位数才尽量少)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; int len(LL x){int i=0;while(x){++i;x/=10;}return i;} int work(int l,LL a,LL b){ for (LL i=1LL;i<=a;i*=10LL) if (a/i==b/i) return a/i*i; return 0; } int main(){ int t,i,la,lb;LL a,b,ans,ji,j,ci; bool f=false;scanf("%d",&t); while(t--){ scanf("%I64d%I64d",&a,&b); la=len(a);lb=len(b);ji=ans=0; if (la==lb){ji=work(la,a,b);a-=ji;b-=ji;} if (!a) ans=0; else{ if (!ji){ for (ans=5LL;ans<=b;ans*=10LL) if (ans>=a) break; if (ans>b){ for (i=1;i<=9;++i){ if (i==5) continue; for (j=(LL)i;j<=b;j*=10LL) if (j>=a) break; if (j<=b) ans=min(ans,j); } } }else{ for (ans=0,f=false,ci=5LL;ci<=b;ci*=10LL) if (ci>=a){ ans=ci;f=true; } for (i=9;i>=1;--i){ if (i==5) continue; for (j=(LL)i;j<=b;j*=10LL) if (j>=a){ la=len(j);lb=len(ans); if (la>lb){ans=j;f=false;} else{if (la==lb&&!f){ans=j;f=false;}} } } } }printf("%I64d\n",ans+ji); } }
bzoj4300 绝世好题
题目大意:给定ai,求它的一个子序列满足bi&bi-1!=0。
思路:对于每一个ai二进制的1的位置上取一个最大的x,那么到ai的时候最多有x+1个,然后这些1的位置上的数又可以最大变为x+1。
有一个黑科技:__builtin_ctz(i)返回i最后一个1后面0的个数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define up 35 using namespace std; int ai[up]={0}; int lowbit(int x){return x&-x;} int main(){ int n,i,j,ans=0,num,ci;scanf("%d",&n); while(n--){ num=0;scanf("%d",&j); for (i=j;i;i-=lowbit(i)) num=max(num,ai[__builtin_ctz(i)]); ans=max(ans,++num); for (i=j;i;i-=lowbit(i)) ai[ci=__builtin_ctz(i)]=num; }printf("%d\n",ans); }
noip模拟题
题目大意:给定数组a、b,求ai+bj前k大值。
思路:二分最小的那个和,排序a、b后就可以O(n)判断了,最后用前缀和算出答案就可以了。注意可能二分的最小的那个值导致答案比k大,减去多出来的个数*二分的值就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 #define LL long long using namespace std; LL ai[maxm]={0},bi[maxm]={0},sumb[maxm]={0}; int n,m,k; int cmp(LL x,LL y){return x>y;} bool judge(LL x){ int i,j=0,ans=0; for (i=1;i<=n;++i){ while(ai[i]+bi[j+1]>=x&&j<m) ++j; if (ai[i]+bi[j]>=x) ans+=j; if (ans>=k) return true; }return (ans>=k); } int main(){ freopen("pattern.in","r",stdin); freopen("pattern.out","w",stdout); int i,j;LL l,r,mid,ans=0,sum=0,cnt=0;scanf("%d%d%d",&n,&m,&k); for (i=1;i<=n;++i) scanf("%I64d",&ai[i]); for (i=1;i<=m;++i) scanf("%I64d",&bi[i]); sort(ai+1,ai+n+1); sort(bi+1,bi+m+1,cmp); for (i=1;i<=m;++i) sumb[i]=sumb[i-1]+bi[i]; l=0;r=ai[n]+bi[1]; while(l<=r){ mid=(l+r)/2; if (judge(mid)){ans=mid;l=mid+1;} else r=mid-1; }for (j=0,i=1;i<=n;++i){ while(ai[i]+bi[j+1]>=ans&&j<m) ++j; if (ai[i]+bi[j]>=ans){sum+=ai[i]*(LL)j+sumb[j];cnt+=j;} }if (cnt>(LL)k) sum-=(cnt-(LL)k)*ans; printf("%I64d\n",sum); }
bzoj3671 随机数生成器
题目大意:根据给定规则求出数列1~n*m,放入一个n*m的网格中,求一条路径使得路径上的点排升序后字典序最小。
思路:先求出矩阵的样子,然后就是贪心了,尽量选小的,并且把这个点左下角和右上角的置为不能选(如果访问过就可以break,这个标记可以用一个数组,因为这个数组中间是不会有一个点既被从右上到左下标记也被从左下到右上标记)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 5005 #define LL long long using namespace std; LL a,b,c,d;int n,m; int xi[maxm*maxm],ti[maxm*maxm]; void get(int i){ LL x=(LL)xi[i-1]; xi[i]=(int)((a*x*x%d+b*x%d+c)%d); } void flood(int x){ if (x>n*m||ti[x]) return;ti[x]=1; if ((x+m-1)/m<n&&!ti[x+m]) flood(x+m); if ((x-1)%m+1>1&&!ti[x-1]) flood(x-1); } void flood2(int x){ if (x<=0||ti[x]) return;ti[x]=1; if ((x+m-1)/m>1&&!ti[x-m]) flood2(x-m); if ((x-1)%m+1<m&&!ti[x+1]) flood2(x+1); } int main(){ int i,j,q,x,y,k,cn; scanf("%d%I64d%I64d%I64d%I64d",&xi[0],&a,&b,&c,&d); scanf("%d%d%d",&n,&m,&q);k=n*m; for (i=1;i<=k;++i){ti[i]=i;get(i);} for (i=1;i<=k;++i) swap(ti[i],ti[xi[i]%i+1]); for (i=1;i<=q;++i){scanf("%d%d",&x,&y);swap(ti[x],ti[y]);} for (i=1;i<=k;++i) xi[ti[i]]=i; cn=1; if (ti[1]==1||ti[n*m]==1){ ++cn;if (ti[1]==2||ti[n*m]==2) ++cn; }memset(ti,0,sizeof(ti)); for (k=n+m-1,i=1;i<=k;++i) while(1){ x=(xi[cn]+m-1)/m;y=(xi[cn]-1)%m+1;++cn; if (!ti[xi[cn-1]]){ if (y>1) flood(xi[cn-1]+m-1); if (y<m) flood2(xi[cn-1]-m+1); if (i<k) printf("%d ",cn-1); else printf("%d\n",cn-1); break; } } }
bzoj4000 棋盘
题目大意:给定一个nm的棋盘(所有下标从0开始),每个棋子的攻击范围是一个3*p的矩阵,1表示可以攻击,0不可以,棋子在1行k列,求铺棋盘并使棋子互相不攻击的方案数(mod 2^32)。
思路:因为棋子在中间一行,所以可以状压dp一下,同时所有的转移一样,所以可以矩乘。
注意:1)认真读题:所有下标从0开始;2)mod 2^32只能用unsigned int或者快速乘。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 64 #define UI unsigned int using namespace std; struct use{ UI num[N][N]; use operator*(const use&x)const{ use y;int i,j,k; for (i=0;i<N;++i) for (j=0;j<N;++j){ y.num[i][j]=0; for (k=0;k<N;++k) y.num[i][j]+=num[i][k]*x.num[k][j]; }return y;} }a,b; int up,m,k,ci[3]={0}; int calc(int x,int y){ int i,j,cc=0; for (i=0;i<m;++i) if (x&(1<<i)){ if (i>k) cc|=y<<i-k; else cc|=y>>k-i; }return cc; } void mi(int x){ memset(b.num,0,sizeof(b.num)); for (int i=0;i<up;++i) b.num[i][i]=1; for(;x;x>>=1){ if (x&1) b=b*a; a=a*a;}} int main(){ int i,j,n,p,x;UI ans=0; scanf("%d%d%d%d",&n,&m,&p,&k); for (i=0;i<3;++i) for (j=0;j<p;++j){ scanf("%d",&x); ci[i]|=x<<j;} up=1<<m;ci[1]^=1<<k; memset(a.num,0,sizeof(a.num)); for (i=0;i<up;++i){ if (calc(i,ci[1])&i) continue; for (j=0;j<up;++j){ if (calc(j,ci[1])&j) continue; if (calc(i,ci[2])&j) continue; if (calc(j,ci[0])&i) continue; a.num[i][j]=1; } }mi(n-1); for (i=0;i<up;++i) for (j=0;j<up;++j) ans+=b.num[i][j]; cout<<ans<<endl; }
bzoj4401 块的计数
题目大意:求将一棵树划分成块大小一样的方案数。
思路:对于一种块的大小只有一种分割方案,所以可以枚举块的大小x(根n),然后判断能否分成x块,分块的儿子一端的那个点子树和外面的部分的gcd是x的倍数,所以可以预处理出gcd=i的个数,然后暴力统计就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 2000005 using namespace std; int point[N]={0},en[N]={0},next[N]={0},tot=0,cnt[N]={0},siz[N]={0},n; int gcd(int x,int y){return (!y ? x : gcd(y,x%y));} void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u;} void dfs(int u,int ff){ int i,j,v;siz[u]=1; for (i=point[u];i;i=next[i]){ if ((v=en[i])==ff) continue; dfs(v,u);siz[u]+=siz[v]; }if (siz[u]==n) j=0; else j=gcd(siz[u],n-siz[u]); ++cnt[j];} int judge(int x){ int cc=0,i; for (int i=x;i<=n;i+=x) cc+=cnt[i]; return (cc==n/x-1);} int main(){ int i,j,ans=0,u,v;scanf("%d",&n); for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);} dfs(1,0); for (i=1;i*i<=n;++i){ if (n%i) continue; ans+=judge(i)+(i*i==n ? 0 : judge(n/i)); }printf("%d\n",ans); }
bzoj1071 组队
题目大意:给定一些人的身高和速度,求一队人使得a*(h-minh)+b*(s-mins)<=c的最多人数。
思路:暴力是O(n^3)的,然后考虑优化。如果枚举minh和minv之后,能O(1)算出答案就可以了,可以发现,如果按照a*h+b*s排序之后,可行的部分是单调的,但是还有s的限制,所以可以用另一个单调的数组以s来排序,做的时候两个同时单调的扫就可以了。(为什么被卡常数了。。。)
注意:要考虑h的上限限制,所以保证了删除的一定已经加过了。(!!!)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10005 using namespace std; struct use{int h,s,v;}ai[N],bi[N]; int cmp(const use&x,const use&y){return x.v<y.v;} int cmp2(const use&x,const use&y){return x.s<y.s;} int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int main(){ int n,i,j,k1,k2,ans=0,a,b,c,cnt,xx,mh,mx; n=in();a=in();b=in();c=in(); for (i=1;i<=n;++i){ ai[i].h=in();ai[i].s=in(); ai[i].v=a*ai[i].h+b*ai[i].s;bi[i]=ai[i]; }sort(ai+1,ai+n+1,cmp); sort(bi+1,bi+n+1,cmp2); for (i=1;i<=n;++i){ k1=k2=cnt=0; mh=ai[i].h;mx=mh+c/a; for (j=1;j<=n;++j){ xx=a*mh+b*bi[j].s; while(k1<n&&ai[k1+1].v-xx<=c){ ++k1;cnt+=(ai[k1].h>=mh&&ai[k1].h<=mx); }while(k2<n&&bi[k2+1].s<bi[j].s){ ++k2;cnt-=(bi[k2].h>=mh&&bi[k2].h<=mx); }ans=max(ans,cnt); } }printf("%d\n",ans); }
bzoj4007 战争调度(!!!)
题目大意:给定一棵n层的满二叉树,每个节点有两个状态,叶子节点的状态和自己祖先的状态相同的时候会有一定的贡献,问怎么安排状态使得贡献最大。(其中战争状态的叶子节点不能超过m)
思路:暴搜+dp。暴搜每个节点的状态,同时树形dp,fi[i][j]表示i这个点子树中的叶子有j个为战争状态的最大收益。一个第i层的点访问2^(i-1)次,每次都是2*(2^(n-i))^2的复杂度,这一层有2^(i-1)个点,总复杂度大概是(n-1)*2^(2n-1)+2^(n-1)*n。
注意:在满二叉树中,很多复杂度都会变优很多。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 3000 using namespace std; int w[N][N],f[N][N],fi[N][N],n,m,ci[N]; void dp(int u,int sz){ if (u>=(1<<(n-1))){ fi[u][0]=fi[u][1]=0; for (int i=u>>1;i;i>>=1){ if (ci[i]) fi[u][1]+=w[u][i]; else fi[u][0]+=f[u][i]; }return; }int i,j,hi,lo,lm;lm=min(m,sz); for (i=0;i<=lm;++i) fi[u][i]=0; for (ci[u]=0;ci[u]<=1;++ci[u]){ dp(u<<1,sz>>1);dp(u<<1|1,sz>>1); for (i=0;i<=lm;++i){ lo=max(i-(sz>>1),0); hi=min(sz>>1,i); for (j=lo;j<=hi;++j) fi[u][i]=max(fi[u][i],fi[u<<1][j]+fi[u<<1|1][i-j]); } } } int main(){ int i,j,ans=0; scanf("%d%d",&n,&m); for (i=(1<<(n-1));i<(1<<n);++i) for (j=i>>1;j;j>>=1) scanf("%d",&w[i][j]); for (i=(1<<(n-1));i<(1<<n);++i) for (j=i>>1;j;j>>=1) scanf("%d",&f[i][j]); dp(1,1<<(n-1)); for (i=0;i<=m;++i) ans=max(ans,fi[1][i]); printf("%d\n",ans); }
bzoj3229 石子合并
题目大意:石子合并数据超大版。
思路:引入一种GarsiaWachs算法。每次找到i最小的一个ai[i-1]<ai[i+1]的位置,把ai[i]和ai[i-1]合并,然后作为一个石子放入i之前离它最近的比它大的那个石子后面。(具体的证明并不是很清楚,希望有大神能指点一下。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 40005 #define LL long long using namespace std; int ai[maxnode]={0},t; LL ans=0; void work(int x) { int tmp=ai[x]+ai[x-1];ans+=(LL)tmp;int i,j; for (i=x;i<t-1;++i) ai[i]=ai[i+1]; --t; for (i=x-1;i&&ai[i-1]<tmp;--i) ai[i]=ai[i-1]; ai[i]=tmp; while(i>=2&&ai[i-2]<=ai[i]){ j=t-i;work(i-1);i=t-j; } } int main() { int i,j,n;scanf("%d",&n); for (i=0;i<n;++i) scanf("%d",&ai[i]); t=1; for (i=1;i<n;++i){ ai[t++]=ai[i]; while(t>=3&&ai[t-3]<=ai[t-1]) work(t-2); } while(t>1) work(t-1); printf("%I64d\n",ans); }
bzoj4547 小奇的集合
题目大意:可重集s,每次选两个数a、b,把a+b放入集合,进行k次操作,问最后s和的最大值。
思路:找到最大和次大值:如果都小于0,就每次选a、b;如果都大于0,就用矩乘转移;如果一个大于0、一个小于0,先模拟过程到都大于0,再矩乘。
注意:(1)答案要%p;(2)模拟过程的时候不要忘记统计答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 3 #define M 100005 #define LL long long #define p 10000007LL #define inf 2100000000LL using namespace std; struct mat{ LL nm[N][N]; void init(){ memset(nm,0,sizeof(nm)); for (int i=0;i<N;++i) nm[i][i]=1LL; } mat operator*(const mat&x)const{ mat c;int i,j,k; for (i=0;i<N;++i) for (j=0;j<N;++j){ c.nm[i][j]=0LL; for (k=0;k<N;++k) c.nm[i][j]=(c.nm[i][j]+nm[i][k]*x.nm[k][j]%p)%p; }return c; } }zh,ji; LL mx,cmx,ci=0LL; mat mi(mat x,int y){ mat a;a.init(); for (;y;y>>=1){ if (y&1) a=a*x; x=x*x; }return a;} int main(){ int n,i,k;LL x,sm=0LL;mx=cmx=-inf; scanf("%d%d",&n,&k); for (i=1;i<=n;++i){ scanf("%I64d",&x);sm+=x; if (x>=mx){cmx=mx;mx=x;} else{if (x>cmx) cmx=x;} }sm=(sm%p+p)%p; if (mx<=0LL) ci=(cmx+mx)%p*(LL)k%p; else{ for (;cmx<0LL;){cmx+=mx;ci+=cmx;--k;} memset(zh.nm,0,sizeof(zh.nm)); memset(ji.nm,0,sizeof(ji.nm)); zh.nm[0][0]=zh.nm[0][1]=zh.nm[0][2]= zh.nm[1][0]=zh.nm[2][2]=1LL; ji.nm[0][0]=mx+cmx;ji.nm[0][1]=mx; ji=ji*mi(zh,k);ci+=ji.nm[0][2]; }printf("%I64d\n",(ci+sm)%p); }
bzoj4506 Fort Moo
题目大意:给定一个n*m的网格(有障碍点),求一个边框不经过障碍的最大面积的矩形。
思路:O(n^4)暴力可以过。考虑优化:枚举矩形上下边界,对于左右边界可以单调扫一遍,O(n^3)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 205 using namespace std; int sm[2][N][N]; int in(){ char ch=getchar(); while(ch!='.'&&ch!='X') ch=getchar(); return ch=='X';} int main(){ int n,m,i,j,p,q,ans=0; scanf("%d%d",&n,&m); memset(sm,0,sizeof(sm)); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ p=in(); sm[0][i][j]=sm[0][i][j-1]+p; sm[1][i][j]=sm[1][i-1][j]+p; } for (i=1;i<=n;++i) for (j=i+1;j<=n;++j){ for (p=q=1;q<=m;++q){ if (sm[1][j][q]-sm[1][i-1][q]) continue; while ((p<=q)&&(sm[1][j][p]-sm[1][i-1][p]||sm[0][i][q]-sm[0][i][p-1] ||sm[0][j][q]-sm[0][j][p-1])) ++p; ans=max(ans,(j-i+1)*(q-p+1)); } } printf("%d\n",ans); }
bzoj3953 Self-Assembly
题目大意:给定n个正方形零件,每条边上有两种符号:1)'A'...'Z'+'+'/'-';2)'00'。其中字母一样符号相反的边可以相邻,问n个零件能否组成无限的大零件。
思路:如果给能相邻的零件连边,如果有环就一定可以组成无限的大零件,但这样可能出现两个零件和某个零件的一条边相邻的情况,拆点,然后dfs找环就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 40005 #define M 8 #define nn 1000000 using namespace std; int point[nn],next[nn],en[nn],tot,n; char ss[M]; bool vi[nn],vv[nn]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;} int idx(char s1,char s2){return 4*n+(s1-'A')*2+(s2=='-' ? 1 : 2);} int idn(int a,int b){return (a-1)*4+b+1;} bool dfs(int u){ int i,v;vi[u]=vv[u]=true; for (i=point[u];i;i=next[i]){ if (vi[v=en[i]]) return false; if (vv[v]) continue; if (!dfs(v)) return false; }vi[u]=false; return true;} int main(){ int i,j,k; while(scanf("%d",&n)==1){ tot=0; memset(point,0,sizeof(point)); memset(vi,false,sizeof(vi)); memset(vv,false,sizeof(vv)); for (i=1;i<=n;++i){ scanf("%s",ss); for (j=0;j<8;j+=2){ if (ss[j]=='0') continue; add(idn(i,j/2),idx(ss[j],(ss[j+1]=='-' ? '+' : '-'))); for (k=0;k<8;k+=2) if (k!=j) add(idx(ss[j],ss[j+1]),idn(i,k/2)); } }for (i=4*n+52;i;--i) if (!vv[i]) if (!dfs(i)) break; if (i) printf("unbounded\n"); else printf("bounded\n"); } }
bzoj4052 Magical GCD
题目大意:求给定序列的一个子序列,使得长度*gcd最大。
思路:考虑所有右端点为r的区间,它们的gcd是单调不减,并且有倍数关系的,所以最多有log段,新加入r+1的时候,可以暴力找到所有这些段,更新答案的时候也是暴力找到所有段的左端点处答案最大,取max。可以用map或者线段树上二分解决。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define N 100005 #define LL long long using namespace std; LL gcd(LL a,LL b){return (!b ? a : gcd(b,a%b));} map<LL,int> gp[2]; LL in(){ char ch=getchar();LL x=0LL; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10LL+(LL)(ch-'0');ch=getchar(); }return x;} int main(){ int t,n,i,cur,la;LL ans,x,g;t=(int)in(); map<LL,int>::iterator it; while(t--){ n=(int)in();cur=0; la=1;ans=0LL; gp[cur].clear(); for (i=1;i<=n;++i){ x=in();cur^=1;la^=1; gp[cur].clear(); for (it=gp[la].begin();it!=gp[la].end();++it){ g=gcd(it->first,x); if (!gp[cur].count(g)) gp[cur][g]=it->second; else gp[cur][g]=min(gp[cur][g],i); }g=x; if (!gp[cur].count(g)) gp[cur][g]=i; else gp[cur][g]=min(gp[cur][g],i); for (it=gp[cur].begin();it!=gp[cur].end();++it) ans=max(ans,it->first*(LL)(i - it->second+1)); }printf("%I64d\n",ans); } }
codevs1288 埃及分数
题目大意:把一个分数a/b表示成1/x1+1/x2+...的形式,输出项数最小,项数相同是最小的一项最大的方案。
思路:迭代加深搜索+减枝。每次可以算出小于当前a/b的最大的1/i,然后逐渐变大i,同时保证1/i是单调递减的。最后一项可以算出来,如果搜索的话会tle。
注意:要开longlong。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1005 #define inf 2100000000 #define LL long long using namespace std; LL gcd(LL a,LL b){return (!b ? a : gcd(b,a%b));} struct fen{LL a,b;}; bool operator<(fen x,fen y){return (x.a*y.b<=y.a*x.b);} fen operator-(fen x,fen y){ LL g=x.b*y.b/gcd(x.b,y.b); x.a=x.a*g/x.b-y.a*g/y.b; x.b=g; g=gcd(x.a,x.b); x.a/=g;x.b/=g; return x;} LL ans[N],ci[N]; int n; int gmn(fen x){ for (int i=2;;++i) if (x.b<x.a*i) return i; } void dfs(fen x,int mn,int i){ int j; if (i>=n){ if (x.b%x.a) return; ci[n]=x.b/x.a; if (ci[n]<ans[n]) for (j=1;j<=n;++j) ans[j]=ci[j]; return; }mn=max(mn,gmn(x)); for (j=mn;;++j){ if ((fen){n-i+1,j}<x) break; ci[i]=j; dfs(x-(fen){1,j},j+1,i+1); } } void work(fen x){ for (n=1;;++n){ ans[n]=inf; dfs(x,gmn(x),1); if (ans[n]!=inf){ans[0]=n;return;} } } int main(){ int i;LL a,b; while(scanf("%I64d%I64d",&a,&b)==2){ work((fen){a,b}); for (i=1;i<=ans[0];++i) printf("%I64d ",ans[i]); printf("\n"); } }
noi模拟赛第一场T1(!!!)
题目大意:n个数,m个询问,支持:1)修改某个数;2)给出t种颜色,每种颜色可以染ci个数,sigma(i=1~t)ci=n-1,给出一个位置k,问k这个位置染什么颜色,使得k延伸出去的同颜色的等差数列k+xd(d给出)的权值和的期望最小。
思路:考虑t=1的时候,可以>√n暴力;<√n分块。t>=2的时候,可以发现ci小的时候答案更优,考虑算每个点选的概率,k之外的第x个点的概率p(x)=c(n-1-x,c-x)/c(n-1,c),p(x)=p(x-1)*(c-x+1)/(n-x),因为c<=n/2,所以p(x)大概log(n*10^4(权值大小))次就会小于精度误差了,大概暴力算100左右就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define LL long long #define LD double #define N 100005 #define inf 1e100 #define eps 1e-10 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} int n,ai[N],ci[N],sm[320][N]; int gett(int d,int k){return k+(n-k)/d*d;} int main(){ freopen("lzz.in","r",stdin); freopen("lzz.out","w",stdout); int nn,m,i,j,op,x,y,t,k,d;LD ans,cc,pi; n=in();m=in(); for (i=1;i<=n;++i) ai[i]=in(); memset(sm,0,sizeof(sm)); nn=sqrt(n); for (i=1;i<=nn;++i) for (j=1;j<=i;++j) for (k=j;k<=n;k+=i) sm[i][k]=sm[i][max(k-i,0)]+ai[k]; while(m--){ if ((op=in())==1){ x=in();y=in(); for (i=1;i<=nn;++i) sm[i][gett(i,x)]+=y-ai[x]; ai[x]=y; }else{ t=in();k=in();d=in(); for (i=1;i<=t;++i) ci[i]=in(); if (t==1){ ans=0LL; if (d>nn){ for (i=k;i<=n;i+=d) ans+=(LD)ai[i]; for (i=k-d;i>0;i-=d) ans+=(LD)ai[i]; }else ans=(LD)sm[d][gett(d,k)]; }else{ for (ans=inf;t;--t){ cc=ai[k];pi=1.; for (pi=1.,i=1;k+i*d<=n&&i<=ci[t];++i){ pi*=(LD)(ci[t]-i+1)*1./(LD)(n-i); cc+=pi*(LD)ai[k+i*d]; if (i==100) break; }for (pi=1.,i=1;k-i*d>0&&i<=ci[t];++i){ pi*=(LD)(ci[t]-i+1)*1./(LD)(n-i); cc+=pi*(LD)ai[k-i*d]; if (i==100) break; }ans=min(ans,cc); } }printf("%.4f\n",ans); } } }
noi模拟赛第五场T1(!!!)
题目大意:给出n个数,对第i个数求它和之前数的opt操作最大值和取得最大值的个数。opt是|、&、^,ai<65536。
思路:对ai分为前后8位,fi[i][j]表示前8位是i的数和后8位是j的数后8位opt的最大值,gi[i][j]表示最大值的个数。更新的时候枚举所有的j,查询的时候枚举所有的i。复杂度是O(n√m)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 256 using namespace std; int kk,fi[M][M],gi[M][M]; char ss[M]; int calc(int x,int y){ if (kk==0) return x^y; if (kk==1) return x|y; return x&y;} int main(){ freopen("binary.in","r",stdin); freopen("binary.out","w",stdout); int n,i,j,type,x,a,b,up,v,mx,sm; scanf("%d%s%d",&n,ss,&type); if (ss[0]=='x') kk=0; if (ss[0]=='o') kk=1; if (ss[0]=='a') kk=2; memset(fi,-1,sizeof(fi)); up=(1<<8)-1; for (i=1;i<=n;++i){ scanf("%d",&x); a=x>>8;b=x&up; if (i>1){ mx=-1;sm=0; for (j=0;j<(1<<8);++j){ v=(calc(a,j)<<8)|fi[j][b]; if (v>mx){mx=v;sm=gi[j][b];} else if (v==mx) sm+=gi[j][b]; }printf("%d",mx); if (type) printf(" %d",sm); printf("\n"); }for (j=0;j<(1<<8);++j){ v=calc(b,j); if (v>fi[a][j]){ fi[a][j]=v;gi[a][j]=1; }else if (v==fi[a][j]) ++gi[a][j]; } } }
省队集训R2 day2 T1(!!!)
题目大意:给出一个字符串,每次选出形如XX(相邻的两个子串)的最短(第一关键字)、最靠前的串,删掉一个X,问最后剩下的字符串是什么。
思路:考虑从小到大枚举X的长度i,要判断有无长度为i的串X,类似bzoj2119,每i个为一段,求j和j+i的最长公共后缀(二分+hash),长度为l,然后看之后的i-l位是否相同。如果有长度为i的,就从头到尾扫一遍数组,然后相应的删除。删掉一段之后,可能之后某个X’和之前的X有相交的部分,变量的赋值要注意。前一步的复杂度是nlogn的,后一步的是n√n(1+2+...+√n≈n)的。
注意:判出有这个长度的之后,就可以break,然后统一扫一遍来去掉X的做法才是上面的复杂度。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 50005 #define p 31 #define UL unsigned int #define mid (l+r)/2 using namespace std; int idx(char ch){return ch-'a'+1;} char ss[N]; UL ha[N],mi[N]; int vi[N],n,tt=0; void geth(){ int nn=0; for (int i=1;i<=n;++i) if (vi[i]!=tt){ ss[++nn]=ss[i]; ha[nn]=ha[nn-1]*p+idx(ss[i]); }n=nn; } UL getf(int l,int r){return ha[r]-ha[l-1]*mi[r-l+1];} int main(){ freopen("word.in","r",stdin); freopen("word.out","w",stdout); int i,j,k,l,r; scanf("%s",ss+1); n=strlen(ss+1); ha[0]=0LL;mi[0]=1LL; for (i=1;i<=n;++i) mi[i]=mi[i-1]*p; memset(vi,-60,sizeof(vi)); geth(); for (i=1;i<=n;++i){ for (j=1;j+i<=n;j+=i){ if (ss[j]!=ss[j+i]) continue; l=max(1,j-i+1);r=j; while(l<r){ if (getf(mid,j)==getf(mid+i,j+i)) r=mid; else l=mid+1; }k=i-(j-l+1); if ((!k)||(i+j+k<=n&&getf(j+1,j+k)==getf(j+i+1,j+k+i))) break; }if (j+i<=n){ for (++tt,j=i<<1;j<=n;++j){ if (getf(j-i-i+1,j-i)==getf(j-i+1,j)){ for (k=j-i+1;k<=j;++k) vi[k]=tt; j+=i-1; } }geth(); } }for (i=1;i<=n;++i) putchar(ss[i]); printf("\n"); }
省队集训R2 day7 T1(!!!)
题目大意:一开始有a=1、b=0,有两种操作:1)b=b+1;2)a=a*b,求l~r中通过至多p次操作能得到的数的个数。(1<=l<=r<=10^9,p<=100)
思路:因为最多p次操作,所以质因数最多到p,找出p以内的所有质数,dfs出所有这些质数能得到的r以内的数(10^6级别),排序之后进行dp。fi[i]表示第i个数的最小乘的次数,枚举每次*的数i,从头扫一遍fi数组,用指针k记录fi[j]*i=fi[k]的k,这样的乘的次数+最大的那个乘的数就是操作次数了,记录下那些数可以,再找出l~r中的数就是答案了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 105 #define M 3000005 #define LL long long using namespace std; int l,r,p,ai[M]={0},prime[N]={0},flag[N]={0},fi[M]; bool vi[N]={false},cg[M]={false}; void shai(){ int i,j; for (i=2;i<=p;++i){ if (!flag[i]) prime[++prime[0]]=i; for (j=1;j<=prime[0]&&i*prime[j]<=p;++j){ flag[i*prime[j]]=true; if (i%prime[j]==0) break; } } } void dfs(int i,int cc){ if (cc>r) return; if (i>prime[0]){ ai[++ai[0]]=cc; return; }int pc,j; for (pc=prime[i],j=1;;j*=pc){ if ((LL)j*cc>(LL)r) return; dfs(i+1,cc*j); if ((LL)j*pc>(LL)r) return; } } int main(){ freopen("calc.in","r",stdin); freopen("calc.out","w",stdout); int i,j,k,up,cnt=0; scanf("%d%d%d",&l,&r,&p); shai();dfs(1,1); sort(ai+1,ai+ai[0]+1); memset(fi,127/3,sizeof(fi)); fi[1]=0;cg[1]=true; for (up=ai[ai[0]],i=2;i<p;++i) for (j=1,k=1;j<=ai[0]&&ai[j]*i<=up;++j){ while(ai[k]<ai[j]*i) ++k; fi[k]=min(fi[k],fi[j]+1); if (fi[k]+i<=p) cg[k]=true; } for (i=ai[0];i;--i) if (ai[i]>=l&&cg[i]) ++cnt; printf("%d\n",cnt); }