贪心
CODEVS1198国王游戏
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
思路:首先我们分析一下 如果i和j两个相邻那么i排在j前面的必要条件是 total * a[i] / b[j] < total * a[j] /b[i] ,也就是说 a[i]*b[i] < a[j]*b[j],这样就立刻确定了顺序。接下来就是模拟出每个大臣得到的数,直接使用一下高精度计算即可。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; struct use{ int le,ri,sum,p; }a[1001]; struct use1{ int l,num[10001]; }c[1001],d[1001],ans; int my_comp(const use&x,const use&y) { if (x.sum<y.sum) return 1; else return 0; } struct use1 mul(struct use1 x,int y) { int i,g=0; for (i=1;i<=x.l;++i) { x.num[i]=x.num[i]*y+g; g=x.num[i]/10; x.num[i]=x.num[i]%10; } while (g>0) { ++x.l; x.num[x.l]=g%10; g=g/10; } return x; } struct use1 div(struct use1 x,int y) { int i,j,g=0; struct use1 anss; anss.l=x.l; for (i=x.l;i>=1;--i) { g=g*10+x.num[i]; anss.num[i]=g/y; g=g%y; } while (anss.l>1&&anss.num[anss.l]==0) --anss.l; return anss; } struct use1 maxn(struct use1 x,struct use1 y) { int i; if (x.l>y.l) return x; if (y.l>x.l) return y; if (y.l==x.l) { for (i=x.l;i>=1;--i) { if (x.num[i]>y.num[i]) return x; if (x.num[i]<y.num[i]) return y; } return x; } } int main() { int n,i,j,t; memset(ans.num,0,sizeof(ans.num)); ans.l=0; cin>>n; cin>>a[0].le>>a[0].ri; a[0].sum=a[0].le*a[0].ri; a[0].p=0; for (i=1;i<=n;++i) { cin>>a[i].le>>a[i].ri; a[i].sum=a[i].le*a[i].ri; a[i].p=i; } sort(a+1,a+n+1,my_comp); t=a[0].le; while (t>0) { ++c[0].l; c[0].num[c[0].l]=t%10; t=t/10; } for (i=1;i<=n;++i) { c[i]=mul(c[i-1],a[i].le); d[i]=div(c[i-1],a[i].ri); if (a[i].p!=0) ans=maxn(ans,d[i]); } for (i=ans.l;i>=1;--i) cout<<ans.num[i]; cout<<endl; }
bzoj1148 挂缀
题目大意:已知一些珠子的重量和能承受的重量(不包含本身),求能组成串的最长长度和总重量,若有多种方案长度相同,输出总重量最小的。
思路:这道题目一看就觉得是贪心,不过贪了几种都不正确。我们从串的底部考虑。
有一个小小的结论就是取得时候是按照c[i]+w[i]递增的顺序取得。证明如下:若我们将i挂在i+1上,能取得重量为min(c[i]①,c[i+1]-w[i]②),如果交换后更优,就有取得重量为min(c[i]-w[i+1]③,c[i+1]④),其中我们能看出①>③,④>②,所以我们只要比较②和③的大小就可以了,③<②,就有c[i]+w[i]<c[i+1]+w[i+1],得到了我们的结论。
但还要注意,在做的时候,我们从头扫一遍,如果可以放就放进去,如果不能放,就比较一下当前重量和选中的最大重量,如果当前重量小,那么就把当前珠子选上而舍弃之前那个最大重量的珠子,因为之后的这个重量小而且承受能力大,所以是十分优越的。
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #define maxnode 200005 using namespace std; struct use{ long long ci,wi,si; }ai[maxnode]; priority_queue <long long> que; int cmp(const use x,const use y){return x.si<y.si;} int main() { int i,j,n; long long ans=0,sum=0; scanf("%d",&n); for (i=1;i<=n;++i){scanf("%lld%lld",&ai[i].ci,&ai[i].wi);ai[i].si=ai[i].ci+ai[i].wi;} sort(ai+1,ai+n+1,cmp); for (i=1;i<=n;++i) { if (ai[i].ci>=sum){que.push(ai[i].wi);sum+=ai[i].wi;++ans;} else { if ((j=que.top())>ai[i].wi) {que.pop();que.push(ai[i].wi);sum+=ai[i].wi-j;} } } printf("%lld\n%lld\n",ans,sum); }
bzoj1086 王室联邦
题目大意:已知一棵树,将节点分成一些部分,称为省,要求省内结点个数在b~3b之间,且省会可以在省内或省外,但省内点到省会路线上的点都在省内,输出分省方案。
思路:dfs贪心暴力。如果siz>b就分成一个省,但有可能一个子树的节点不够一个省的,在递归下一棵子树时,可能会分成不连通的一个省,所以我们用一个栈,递归到时进栈,分省时把这一点之上的都分成一个省,之下的不会错分。最后可能会剩下一些点没有分,直接放到最后一个省里一定能够联通且符合要求。最后扫一遍所有的省,如果有节点数>3b的就是无合法方案,如果n<b也无。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1005 using namespace std; int point[maxnode]={0},next[maxnode*2]={0},en[maxnode*2]={0},tot=0,cnt=0,n,b,siz[maxnode]={0},ss[maxnode]={0}, sh[maxnode]={0},pro[maxnode]={0},zhan[maxnode]={0}; void add(int u,int v) { ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v; ++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; } void dfs(int u,int fa) { int i,j; zhan[++tot]=u; for (i=point[u];i;i=next[i]) if (en[i]!=fa) { dfs(en[i],u);siz[u]+=siz[en[i]]; if (siz[u]>=b) { pro[++cnt]=u;ss[cnt]=siz[u]; while(zhan[tot]!=u) sh[zhan[tot--]]=cnt; siz[u]=0; } } ++siz[u]; } int main() { int i,j,u,v;bool f=false; scanf("%d%d",&n,&b); for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);} tot=0;dfs(1,0); if (n<b) f=true; for (i=1;i<=n;++i) if (!sh[i]){sh[i]=cnt;++ss[cnt];} for (i=1;i<=cnt;++i) if (ss[i]<b||ss[i]>=3*b) f=true; if (f) printf("0\n"); else { printf("%d\n",cnt); for (i=1;i<=n;++i) printf("%d ",sh[i]); printf("\n"); for (i=1;i<=cnt;++i) printf("%d ",pro[i]); printf("\n"); } }
bzoj1045 糖果传递
题目大意:给出n个人的糖果数量,求使每个人糖果均分交换的最小糖果数量。
思路:设M表示均分后每个人的糖果数,对每一个人可以列出方程组:Ai-xi+xi+1(i=n时i+1=1)=M,=>xi=x1-ci(ci=A1+A2+...+Ai-i*M),这里有一些化简技巧。然后我们要|x1|+|x1-c1|+...+|x1-cn-1|,所以对c0~cn-1(c0=0)排序后,取中位数,在带回算出这个答案就行了。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define maxnode 1000005 #define LL long long using namespace std; LL ai[maxnode]={0},ci[maxnode]={0}; int main() { LL ans=0,m=0;int i,j,n; scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%I64d",&ai[i]);m+=ai[i]; } m/=n; for (i=1;i<=n;++i) ci[i]=ai[i]-m+ci[i-1]; sort(ci,ci+n);LL x1=ci[n/2]; for (i=1;i<=n;++i) ans+=abs(x1-ci[i-1]); printf("%I64d\n",ans); }
bzoj3613 南园满地堆轻絮
题目大意:给定一个序列a,把它转化成一个b序列使得b不降,代价是max(abs(ai-bi)),求最小代价。
思路:贪心构造一下,答案其实就是最大的(逆序对差值+1)/2。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define maxnode 5000005 using namespace std; LL ai[maxnode]={0}; int p,sa,sb,sc,sd; LL f(LL x){return (((sa*x%p*x%p*x%p+sb*x%p*x%p)%p+sc*x%p)%p+sd)%p;} int main() { int n,i,j;LL maxn,ans=0; scanf("%d%d%d%d%d%I64d%d",&n,&sa,&sb,&sc,&sd,&ai[1],&p); for (i=2;i<=n;++i) ai[i]=(f(ai[i-1])+f(ai[i-2]))%p;maxn=ai[1]; for (i=2;i<=n;++i) { if (maxn<=ai[i]) maxn=ai[i]; else ans=max(ans,(maxn-ai[i]+1)/2); } printf("%I64d\n",ans); }
bzoj1078 斜堆
题目大意:给定一棵斜堆的形态(节点编号0~n,根为0),求一个字典序最小的插入的顺序使得最后的斜堆是给定的形态。
思路:考虑斜堆插入的时候,最后一个节点一定是这个斜堆中的一个极左节点,同时没有右子树。所以我们在找插入顺序的时候,每次找到最靠上的没有右子树的极左节点(这里如果它的左孩子是叶子节点,就取它的孩子,这样会更优),然后把祖先上的左右儿子都交换。最后就可以找到了。
求逆操作的时候,可以考虑原操作的最后一次的特点,从而推出逆操作倒着做的特点,注意搞懂操作的含义和特殊情况。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1005 using namespace std; int fa[maxnode]={0},ch[maxnode][2]={0},ans[maxnode]={0},n,j; bool ye(int u){return (!ch[u][0]&&!ch[u][1]?1:0);} int find(int u) { if (!ch[u][1]) return ((ch[u][0]&&ye(ch[u][0]))?ch[u][0]:u); else return find(ch[u][0]); } void work() { int root=1,i,j,u; for (i=n;i>=0;--i) { u=find(root);ans[i]=u; if (root==u) { root=ch[u][0];fa[root]=0; } else { if (ch[u][0]) { fa[ch[u][0]]=fa[u];ch[fa[u]][0]=ch[u][0]; } else ch[fa[u]][0]=0; u=fa[u]; while(u){swap(ch[u][0],ch[u][1]);u=fa[u];} } } } int main() { int i,k;scanf("%d",&n); for (i=2;i<=n+1;++i) { scanf("%d",&j);++j; if (j<=100){fa[i]=j;ch[j][0]=i;} else {fa[i]=j-100;ch[j-100][1]=i;} }work(); for (i=0;i<=n;++i) printf("%d ",ans[i]-1); printf("\n"); }
bzoj1034 泡泡堂
题目大意:田忌赛马,只是要同时求可能的最大和最小值。
思路:其实最小值也就是2n-对方的最大值。考虑求我方的最大值,贪心,但是要首尾都贪心,如果当前比对方大就可以直接赢,如果小,就兑掉对方最大的;如果前后双方对应的都一样,就用我方小的兑大的(有可能相等就判断一下)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 using namespace std; int ai[maxm]={0},bi[maxm]={0},n,x[maxm],y[maxm]; int calc(int t){ int i,j,sum=0;x[0]=y[0]=n; if (t)for (i=1;i<=n;++i){x[i]=ai[i];y[i]=bi[i];} else for (i=1;i<=n;++i) {x[i]=bi[i];y[i]=ai[i];} i=j=1; while(i<=x[0]){ if (x[i]>y[j]){sum+=2;++i;++j;} else{ if (x[x[0]]>y[y[0]]){sum+=2;--x[0];--y[0];} else{sum+=(x[i]==y[y[0]]);++i;--y[0];} } }return sum; } int main(){ int i,j;scanf("%d",&n); for (i=1;i<=n;++i)scanf("%d",&ai[i]); for (i=1;i<=n;++i)scanf("%d",&bi[i]); sort(ai+1,ai+n+1);sort(bi+1,bi+n+1); printf("%d %d\n",calc(1),2*n-calc(0)); }
bzoj3969 Low Power
题目大意:给定n个机器,每个机器有两块芯片,每块芯片上能放k个电池,一个芯片的电量为最小的电池电量,两芯片电量差越小一个机器的作用越大。求最大电量差最小是多少。
思路:又是一个最大值最小的问题,所以二分。然后就是对于某一个值x的判断,排序后,从前往后扫一遍,如果i和i+1差值小于x,就选上,同时跳过他后面的那一个点(不能连续选三个点及以上的),但是如果这个值后面的电池数量不够剩下的分组j(包含当前组)用的了(即2nk-i+1<2kj)就肯定不符合了,最后看能不能选到n个就可以了。
其实最开始是想写线段树贪心的,但是发现有很多问题处理不了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 1000005 #define inf 1000000000 using namespace std; int ai[maxm]={0},n,k,up; bool judge(int x){ int i,j; for (j=0,i=1;j<n&&i<up;++i) if (ai[i+1]-ai[i]<=x){ if (up-i+1<(n-j)*2*k) return false;++i;++j; }return (j==n?true:false); } int main(){ int i,j,l,r,mid;scanf("%d%d",&n,&k); up=2*n*k; for (i=1;i<=up;++i)scanf("%d",&ai[i]); sort(ai+1,ai+up+1); l=ai[2]-ai[1];r=ai[up]; while(l<r){ mid=(l+r)/2; if (judge(mid)) r=mid; else l=mid+1; }printf("%d\n",l); }
bzoj3174 拯救小矮人
题目大意:给定n个小矮人和他们身高和手长,所有下面小矮人的身高加上最顶上的身高加上他的手长如果大于井高就可以出去,求最多能出去多少个。
思路:一眼觉得贪心,然后随便按身高+手长的递增序排序,然后从上到下的顺序取,但是这样wa了,加了各种可能接近答案的东西都不对。然后无奈下只能往dp上想,fi[i]表示逃出i个人后的最高高度,fi[0]=sigma(i=1~n)身高,然后像背包那样更新(二维的转移莫名其妙的wa了)。但是这样一开始人的顺序不同,dp结果不同,所以又得找到一种最好的贪心策略,然后一开始的贪心又用上了,最后最大的i满足fi[i]>=0就是答案了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 2005 using namespace std; struct use{ int ai,bi,ci; }ri[maxm]={0}; int fi[maxm]={0}; int cmp(const use&x,const use&y){return x.ci<y.ci;} int main(){ int n,i,j,h,ans=0;scanf("%d",&n); memset(fi,128,sizeof(fi));fi[0]=0; for (i=1;i<=n;++i){ scanf("%d%d",&ri[i].ai,&ri[i].bi); ri[i].ci=ri[i].ai+ri[i].bi;fi[0]+=ri[i].ai; }scanf("%d",&h);sort(ri+1,ri+n+1,cmp); for (i=1;i<=n;++i){ for (j=i;j>=0;--j){ if (ri[i].bi+fi[j]>=h) fi[j+1]=max(fi[j+1],fi[j]-ri[i].ai); } }for (i=n;i;--i) if (fi[i]>=0) break; printf("%d\n",i); }
bzoj1052 覆盖问题
题目大意:给定n个点,求能覆盖所有点的边平行于坐标轴的三个L*L的正方形,使L最小。
思路:二分+贪心。二分答案,然后就是判断剩下的点能否被正方形覆盖,找出最大最小的横纵坐标,然后枚举四个顶点为顶点的正方形贪心覆盖,然后递归两次进行覆盖,一共相当于16种情况。注意:这里不能进行6种情况的判断,因为最终答案不一定是两个正方形都在最开始最外层矩形的顶点处的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 20005 #define inf 1000000000 using namespace std; struct use{ int x,y; }ai[maxm],bi[4],ci[4]; int n; int ab(int x){return x<0 ? -x : x;} bool cover(int s1,int s2,int l,int dep){ int i,j,x1,x2,y1,y2; x1=y1=inf;x2=y2=-inf; for (i=1;i<=n;++i){ if (max(ab(ai[i].x-bi[s1].x),ab(ai[i].y-bi[s1].y))<=l) continue; if (s2>=0&&max(ab(ai[i].x-ci[s2].x),ab(ai[i].y-ci[s2].y))<=l) continue; x1=min(x1,ai[i].x);x2=max(x2,ai[i].x); y1=min(y1,ai[i].y);y2=max(y2,ai[i].y); }if (dep==2) return (max(x2-x1,y2-y1)<=l); ci[0]=(use){x1,y1};ci[1]=(use){x1,y2}; ci[2]=(use){x2,y1};ci[3]=(use){x2,y2}; for (i=0;i<4;++i) if (cover(s1,i,l,dep+1)) return true; return false; } bool judge(int l){ int i,j; for (i=0;i<4;++i) if (cover(i,-1,l,1)) return true; return false; } int main(){ int i,j,l,r,mid,x1,y1,x2,y2;scanf("%d",&n); x1=y1=inf;x2=y2=-inf; for (i=1;i<=n;++i){ scanf("%d%d",&ai[i].x,&ai[i].y); x1=min(x1,ai[i].x);x2=max(x2,ai[i].x); y1=min(y1,ai[i].y);y2=max(y2,ai[i].y); }l=0;r=max(x2-x1,y2-y1); bi[0]=(use){x1,y1};bi[1]=(use){x1,y2}; bi[2]=(use){x2,y1};bi[3]=(use){x2,y2}; while(l!=r){ if (judge(mid=(l+r)/2)) r=mid; else l=mid+1; }printf("%d\n",l); }
bzoj2563 阿狸和桃子的游戏
题目大意:给定一张无向图(n是偶数),两个人轮流选点,一个人的权值是选的集合中点权加上选的集合中点之间的边权,两人都采取最优策略,问先手比后手高的分数。
思路:点和边都有代价,不好统计,所以考虑先把所有权值减去。如果选一个点,相当于+2*w;不选就是0。对于边:选两点,相当于+2*c,选一个+c,不选+0。这样把点权改为2*w+c,排序之后,因为先手尽量高,而后手不让先手高,所以选偶数个的和。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 using namespace std; int ai[N]={0}; int main(){ int i,n,m,ans=0,u,v,x; scanf("%d%d",&n,&m); for (i=1;i<=n;++i){ scanf("%d",&ai[i]); ans-=ai[i];ai[i]<<=1; }for (i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&x); ans-=x;ai[u]+=x;ai[v]+=x; }sort(ai+1,ai+n+1); for (i=n;i;i-=2) ans+=ai[i]; printf("%d\n",ans); }
bzoj2666 组装(!!!)
题目大意:有m个工厂生产零件pi,位置是xi,建一个加工工厂,使每种零件到加工工厂的距离平方和最小,输出加工工厂的坐标。
思路:对答案化一下,dis=n ans^2 -2ans sigma(xi)+sigma(xi^2),当ans=sigma xi/n时,dis min=(n*sigma(xi^2)-(sigma xi)^2)/n(一),所以min(一)。具体做法是:先选每种的第一个,更新一个答案,然后按同类相邻两个坐标和排序之后,依次考虑是否替换成后一个。
这样做法的正确性的证明:...
注意:维护的rm和sm要不断更新保证是当前的,同时还要维护最优解。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10005 #define M 100005 #define LL long long #define LD double #define eps 1e-7 using namespace std; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} struct use{LD x;int p;}ai[M],zh[M]; int point[N]={0},next[M]={0},la[M]; LD sqr(LD x){return x*x;} int mp(const use&x,const use&y){return cmp(x.x,y.x)<0;} int main(){ int n,m,i,j,ne,zt=0;LD rm=0.,sm=0.,mn,ans; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) scanf("%lf%d",&ai[i].x,&ai[i].p); for (i=m;i;--i){ next[i]=point[ai[i].p];point[ai[i].p]=i; if (next[i]){ zh[++zt]=(use){ai[i].x+ai[next[i]].x,next[i]}; la[next[i]]=i; } }sort(zh+1,zh+zt+1,mp); for (i=1;i<=n;++i){ rm+=(LD)n*1.*sqr(ai[point[i]].x); sm+=ai[point[i]].x; }ans=sm/(LD)n;mn=rm-sqr(sm); for (i=1;i<=zt;++i){ rm=rm-(LD)n*1.*(sqr(ai[la[zh[i].p]].x)-sqr(ai[zh[i].p].x)); sm=sm-(LD)(ai[la[zh[i].p]].x-ai[zh[i].p].x); if (cmp(rm-sqr(sm),mn)<0){ mn=rm-sqr(sm); ans=sm/(LD)n; } }printf("%.4f\n",ans); }
bzoj1237 配对
题目大意:给出长度为n的数列ai和bi,ai、bi中数分别不相同,问一一配对(数大小一样的不能配对)的差的绝对值最小是多少。
思路:从小到大排序后贪心选位置接近的,可以把一段一段的对应看作一个一个的置换,因为尽量选位置靠近的,所以置换的大小是1、2、3(要能表示出所有长度!!!),所以就可以dp一下,fi[i]表示匹配前ai和前bi的最小代价。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define LL long long using namespace std; LL ai[N],bi[N],fi[N],inf; LL ab(LL x){return x<0LL ? -x : x;} LL getp(int i,int j){return ai[i]==bi[j] ? inf : ab(ai[i]-bi[j]);} int main(){ int n,i;scanf("%d",&n); for (i=1;i<=n;++i) scanf("%I64d%I64d",&ai[i],&bi[i]); sort(ai+1,ai+n+1); sort(bi+1,bi+n+1); memset(fi,127/3,sizeof(fi)); inf=fi[0];fi[0]=0; for (i=1;i<=n;++i) fi[i]=min(fi[i-1]+getp(i,i),min((i>1 ? fi[i-2]+getp(i,i-1)+getp(i-1,i) : inf), (i>2 ? min(fi[i-3]+getp(i-2,i)+getp(i-1,i-2)+getp(i,i-1), min(fi[i-3]+getp(i-2,i-1)+getp(i-1,i)+getp(i,i-2), fi[i-3]+getp(i-2,i)+getp(i-1,i-1)+getp(i,i-2))) : inf))); if (fi[n]>=inf) printf("-1\n"); else printf("%I64d\n",fi[n]); }
(一开始只想到了置换1、2的情况,后来对拍才发现3 1 1 2 2 3 3的情况是长度为3的置换)
bzoj4567 背单词
题目大意:对于给定的n个单词安排一个顺序,使得代价和最小。代价和的计算方式如下:对于单词x,(1)如果存在一个x的后缀没有出现在x之前,代价为n*n;(2)如果没有x的后缀,代价为x;(3)如果x的后缀已经填了,最后一个出现在y,代价为x-y。
思路:对单词倒着建trie,扫着贪心,类似等待接水问题,先做单词结尾少的子树。
注意:(1)这里的子树是以某个单词结尾为根的子树;
(2)答案会爆int。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 510005 #define M 26 #define LL long long using namespace std; char ss[N]; int tt=0,ai[N]={0},az=0,cz=0; LL ans=0LL; struct use{ int v,sz,ch[M]; void init(){v=sz=0;memset(ch,0,sizeof(ch));} }tr[N]; struct uu{ int po,sz; bool operator<(const uu&x)const{return sz<x.sz;} }ci[N]; int idx(char ch){return ch-'a';} void ins(){ int i,j,l,u=0;l=strlen(ss); for (i=l-1;i>=0;--i){ if (!tr[u].ch[j=idx(ss[i])]){ tr[u].ch[j]=++tt; tr[tt].init(); }u=tr[u].ch[j]; }tr[u].v=1; } void dfs(int u){ int i,v;tr[u].sz=tr[u].v; for (i=0;i<M;++i){ if (!(v=tr[u].ch[i])) continue; dfs(v);tr[u].sz+=tr[v].sz; } } void getc(int u){ int i,v; if (tr[u].v){ ci[++cz]=(uu){u,tr[u].sz}; return; }for (i=0;i<M;++i){ if (!(v=tr[u].ch[i])) continue; getc(v); } } void geta(int u,int f){ int i,v,l,r; if (u){ ai[u]=++az; ans+=(LL)(az-ai[f]); }for (l=cz+1,i=0;i<M;++i){ if (!(v=tr[u].ch[i])) continue; getc(v); }if (l>cz) return; sort(ci+l,ci+cz+1);r=cz; for (i=l;i<=r;++i) geta(ci[i].po,u); } int main(){ int n,i;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%s",ss);ins(); }dfs(0);geta(0,0); printf("%I64d\n",ans); }
poj1818 ATP
题目大意:给定n和k(n是2的幂),n支球队采用淘汰赛制进行比赛,排名差超过k的、排名靠前的赢,其他的输赢不定,求最差排名为几的队伍可能赢。
思路:排名小的才是排名靠前的(!!!)。可以二分答案,判断的时候贪心,逆着考虑球赛的顺序(决赛->半决赛->...),一开始只有二分的那个队伍留下,每次让留下的队伍赢能赢的里面最大的,进行logn轮,如果最后1~x-1的队伍中有没有被打败的,就是不合法。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 5005 using namespace std; int n,k,zh[N],up,vi[N]={0}; bool judge(int x){ int i,j,a,zt=0,zz; zh[++zt]=x; for (i=1;i<=up;++i) for (zz=zt,j=1;j<=zz;++j) for (a=max(1,zh[j]-k);a<x;++a) if (vi[a]!=x){ zh[++zt]=a; vi[a]=x;break; } for (i=1;i<x;++i) if (vi[i]!=x) return false; return true; } int main(){ int l,r,mid,ans;scanf("%d%d",&n,&k); for (up=0;(1<<(up+1))<=n;++up); l=1;r=n; while(l<=r){ mid=(l+r)>>1; if (judge(mid)){ans=mid;l=mid+1;} else r=mid-1; }printf("%d\n",ans); }
bzoj3983 Takeover Wars(!!!)
题目大意:两个公司有一些子公司,有权值,轮流操作,每个公司可以合并两个自己的子公司、权值为和;用自己的一个子公司吞掉对面的一个子公司,要求自己的权值比对面的大。没有子公司的输。问谁赢。
思路:尽量合并,必要的时候吞掉对面的。
注意:很多情况需要判断,自己一开始写的时候考虑的很少。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define N 100005 #define LL long long using namespace std; priority_queue<LL>ai[2]; int n,m; int solve(){ int d;LL m0,m1,m2,m3; for (d=0;;d^=1){ if (ai[d].empty()) return d^1; if (ai[d].size()==1){ if (ai[d^1].top()>ai[d].top()) return d^1; if (ai[d^1].top()<ai[d].top()) ai[d^1].pop(); else if (ai[d^1].size()==1) return 1; }else{ m0=ai[d].top();ai[d].pop(); m1=ai[d].top();ai[d].pop(); if (ai[d^1].size()==1) ai[d].push(m0+m1); else{ m2=ai[d^1].top();ai[d^1].pop(); m3=ai[d^1].top();ai[d^1].pop(); if (m2>=m0){ ai[d].push(m0+m1); ai[d^1].push(m2);ai[d^1].push(m3); }else{ if (m0+m1>m2+m3){ ai[d].push(m0+m1); ai[d^1].push(m2);ai[d^1].push(m3); }else{ ai[d].push(m1);ai[d].push(m0); ai[d^1].push(m3); } } } } } } int main(){ int i,t=0;LL x; while(scanf("%d%d",&n,&m)==2){ for (i=0;i<2;++i) while(!ai[i].empty()) ai[i].pop(); for (i=1;i<=n;++i){ scanf("%I64d",&x); ai[0].push(x); }for (i=1;i<=m;++i){ scanf("%I64d",&x); ai[1].push(x); }if (!solve()) printf("Case %d: Takeover Incorporated\n",++t); else printf("Case %d: Buyout Limited\n",++t); } }