ZOJ Monthly, March 2013 总结
A.A Simple Tree Problem
刚开始没想好怎么转化,其实,用vector记录下来每个父节点的直接孩子,来个深度优先遍历进行编号,就可以把树形的结构转化成一维的线性结构,然后就是一般的线段树了!
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 vector<int>son[100010]; int l[100010],r[100010];//记录每个节点所对应的区间的左右端点 int sum[400010],flag[400010]; int num; void dfs(int x) { l[x]=num++; int size=son[x].size(); for(int i=0;i<size;i++) { dfs(son[x][i]); } r[x]=num-1; } void pushup(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void pushdown(int rt,int len,int llen) { flag[rt]=0; flag[rt<<1]^=1; flag[rt<<1|1]^=1; sum[rt<<1]=llen-sum[rt<<1]; sum[rt<<1|1]=len-llen-sum[rt<<1|1]; } void update(int s,int e,int l,int r,int rt) { if(s<=l&&e>=r) { flag[rt]^=1; sum[rt]=r-l+1-sum[rt]; return; } int m=(l+r)>>1; if(flag[rt]) pushdown(rt,r-l+1,m-l+1); if(m>=s) update(s,e,lson); if(m<e) update(s,e,rson); pushup(rt); } int query(int s,int e,int l,int r,int rt) { if(s<=l&&e>=r) return sum[rt]; int ret=0; int m=(l+r)>>1; if(flag[rt]) pushdown(rt,r-l+1,m-l+1); if(m>=s) ret+=query(s,e,lson); if(m<e) ret+=query(s,e,rson); pushup(rt); return ret; } int main() { int n,m,temp,i; char ch[10]; while(scanf("%d%d",&n,&m)!=EOF) { for(i=1;i<=n;i++) { son[i].clear(); } for(i=2;i<=n;i++) { scanf("%d",&temp); son[temp].push_back(i); } num=1; dfs(1); memset(sum,0,sizeof(sum)); memset(flag,0,sizeof(flag)); while(m--) { scanf("%s %d",ch,&temp); if(ch[0]=='o') { update(l[temp],r[temp],1,n,1); } else { printf("%d\n",query(l[temp],r[temp],1,n,1)); } } printf("\n"); } return 0; }
B.The Review Plan I
其实从推导来说,这道题比C要水多了...甚B至根本就不用推导,直接用容斥就行了,比赛的时候一直想着,把每个位置不能放的点用vector存下来,但是这样大大增加了容斥原理实现的难度,当我们求有两个位置放错的情况数时,如果这两个位置都不能放X号章节,那么情况数求起来就会很麻烦,直到最后都没得出一个可用的公式..
正确的思路是,记录m个条件,直接对这些条件进行容斥原理,但是有一个额外的条件,就是不能同时选对同一个位置的限制或者对同一个章节的限制,这里我加了两个check数组来记录...然后剩下的就是最裸的容斥了。
还有很坑爹的一点!!!限制是可能重复的,要加判重!!
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MOD 55566677 int check[55],check2[55];//标记天数和章节 int n,m,mark[55][55]; long long fa[100],ans; struct node { int a,b; }va[30]; void dfs(int pos,int num) { if(pos==(m+1)) { if(num&1) ans=(ans-fa[n-num])%MOD; else ans=(ans+fa[n-num])%MOD; } else { dfs(pos+1,num); if(!check[va[pos].a]&&!check2[va[pos].b]) { check[va[pos].a]=1; check2[va[pos].b]=1; dfs(pos+1,num+1); check[va[pos].a]=0; check2[va[pos].b]=0; } } } int main() { int i; fa[0]=1; for(i=1;i<=60;i++) { fa[i]=(fa[i-1]*i)%MOD; } while(scanf("%d%d",&n,&m)!=EOF) { memset(mark,0,sizeof(mark)); for(i=1;i<=m;i++) { scanf("%d%d",&va[i].a,&va[i].b); if(!mark[va[i].a][va[i].b]) { mark[va[i].a][va[i].b]=1; } else { i--; m--; } } ans=0; dfs(1,0);//(当前条件的编号,选中的条件个数) printf("%lld\n",(ans%MOD+MOD)%MOD); } return 0; }
C.The Review Plan II
容斥原理,推起来比较难,研究了好久才弄出来,假设有1个章节所在的天数是错误的情况数是W(1),则每个错误的章节i可以放在i或i+1天,剩下的章节方法数时(n-1)!
所以w(1)=C(n,1)*2*(n-1)!,这一步很容易想到,有点坑的是2个以上的情况,假设有k个章节所在的天数是错误的情况数是W(K),总共n个章节分别记为1,2,...n,它们可放的天数可以表示为(1,2)(2,3)(3,4)...(n,1)现在我们把括号去掉即得到一个数列1,2,2,3,3,4,....n,1,现在我们从里面取出K个数,分别表示k个章节所在的天数,只要满足(1).取出的任何2数不相邻 (即不能取自同一个括号,且不会取到相同的数) (2)两个1不同时被取到。那么,剩下的元素的摆放方法就是(n-k)!,现在只要求出满足这两个条件的取法数就可以了。
把问题单独拿出来,现在我们求一个子问题,1,2,3,....n中能够取出多少个长为k的递增子序列a1,a2...ak,使得ai+1-ai>1
设已经取出了满足上述条件的子序列,我们构造一个新的序列,b1,b2....bk,其中bi=ai-(i-1);
则bi+1-bi=ai+1-ai-1>0
从而1<=a1=b1<b2<.....<bk<=n-k+1
确定b序列只需要从1,2...n-k+1中取出k个数就行了,然后b序列又可以转化成对应的a序列,所以方法数是C(n-k-1,k)
回到我们本来的问题,满足第一个条件的取法数是C(2n-k+1,k),满足第一个条件,但不满足第二个条件的取法数是C(2n-4-(k-2)+1,k-2)
所以W(K)={C(2n-k+1,k)-C(2n-4-(k-2)+1,k-2)}*(n-k)!
W(K)确定以后就是最裸的容斥原理了~
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<stdio.h> #define MOD 1000000007 long long fa[200010],invfa[200010]; long long quickpow(long long m,long long n,long long p) { long long ans=1; while(n) { if(n&1) ans=(ans*m)%p; n=n>>1; m=(m*m)%p; } return ans; } long long Inv(long long x,long long p) { return quickpow(x,p-2,p); } void init() { fa[0]=1; for(int i=1;i<=200000;i++) { fa[i]=(fa[i-1]*i)%MOD; invfa[i]=Inv(fa[i],MOD); } invfa[0]=Inv(fa[0],MOD); } long long W(int n,int k) { long long a=fa[2*n-k+1]; a=(a*invfa[k])%MOD; a=(a*invfa[2*n-k-k+1])%MOD; long long b=fa[2*n-k-1]; b=(b*invfa[k-2])%MOD; b=(b*invfa[2*n-2*k+1])%MOD; return ((fa[n-k]*(a-b))%MOD+MOD)%MOD; } int main() { init(); int n,i; while(scanf("%d",&n)!=EOF) { if(n==1) { printf("0\n"); continue; } long long ans=fa[n]; for(i=1;i<=n;i++) { if(i&1) ans=(ans-W(n,i))%MOD; else ans=(ans+W(n,i))%MOD; } printf("%lld\n",(ans+MOD)%MOD); } return 0; }
D.Digging
比赛的时候不知道为什么一直没看过这道题,直到很多人过了,我们才去看,刚开始也没有思路,觉得很像背包,但是又不像背包,后来ry直接想到了交叉排序...目测这次比赛基本全靠ry= =,我是多能打酱油...
假设要做任务1和2,现在的时间是T,如果先做1,得到的回报是V1=T*s1+(T-t1)*s2,如果先做2得到的回报是V2=T*s2+(T-t2)*s1,V1-V2=t2*s1-t1*s2,因此只要每两个元素都交叉比较来排序就可以得到最优的顺序,考虑到最后快结束的时候,不一定连续取就最优,所以在这个顺序下再用一次01背包就可以了。
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<stdio.h> #include<string.h> #include<stdlib.h> int dp[10100]; struct node { int s,t; }va[3010]; int cmp(const void *a,const void *b) { return (*(node *)a).s*(*(node *)b).t-(*(node *)b).s*(*(node *)a).t; } int MAX(int x,int y) { return x>y?x:y; } int main() { int n,t,i,j; while(scanf("%d%d",&n,&t)!=EOF) { for(i=0;i<n;i++) { scanf("%d%d",&va[i].t,&va[i].s); } qsort(va,n,sizeof(va[0]),cmp); memset(dp,0,sizeof(dp)); for(i=0;i<n;i++) { for(j=t;j>=va[i].t;j--) { dp[j]=MAX(dp[j],dp[j-va[i].t]+j*va[i].s); } } printf("%d\n",dp[t]); } return 0; }
E.Choosing number
这道题也勉强算是数学题吧,居然没一下想到矩阵...好失败ToT
正确的思路是,对于每一个方法数ans[n],分成2部分,一种是ans[n][0],代表n个人排成的,以号码数小于等于K的人结尾的方法数,ans[n][1]代表n个人排成的,以号码数大于K的人结尾的方法数所以:
ans[n+1][0]=ans[n][0]*(k-1)+ans[n][1]*k
ans[n+1][1]=(ans[n][0]+ans[n][1])*(m-k)
这两个式子刚好可以构造一个2X2的矩阵,剩下的只要用矩阵的快速幂就OK了
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<stdio.h> #define MOD 1000000007 #define maxn 6 long long ret[maxn][maxn]; long long init[maxn][maxn]; long long buf[maxn][maxn]; void matrixMul(long long a[][maxn] , long long b[][maxn] , long long n,long long mod) { long long i,j,k; for(i=0;i<n;i++) { for(j=0;j<n;j++) { buf[i][j]=0; } } for(i=0;i<n;i++) { for(k=0;k<n;k++) { if(a[i][k]==0) continue; for(j=0;j<n;j++) { if(b[k][j]==0) continue; buf[i][j]+=a[i][k]*b[k][j]; if (buf[i][j]>=mod||buf[i][j]<=-mod) { buf[i][j]%=mod; } } } } for(i=0;i<n;i++) { for(j=0;j<n;j++) { a[i][j]=buf[i][j]; } } } void matrixMul(long long n,long long m,long long mod) { long long i,j; for(i=0;i<n;i++) { for(j=0;j<n;j++) { ret[i][j]=(i==j); } } for(;m;m>>=1) { if(m&1) { matrixMul(ret,init,n,mod); } matrixMul(init,init,n,mod); } } int main() { int n,m,k; while(scanf("%d%d%d",&n,&m,&k)!=EOF) { init[0][0]=k-1; init[0][1]=m-k; init[1][0]=k; init[1][1]=m-k; matrixMul(2,n-1,MOD); long long ans=(k*ret[0][0]+(m-k)*ret[1][0])%MOD; ans=(ans+k*ret[0][1]+(m-k)*ret[1][1])%MOD; printf("%lld\n",ans); } return 0; }
H.Happy Great BG
其实这题也不能说是精度问题...主要两点易错的是
1.教练俩人也是人(这个直接看出来了...因为样例都过不了)
2.考虑到现实情况,不能直接保留2位小数,只要分以下不是0就要进位,所以后面要加个0.00499!确实很好地结合了实际= =
//http://www.cnblogs.com/SolarWings/archive/2013/04/01/2994548.html #include<stdio.h> int main() { int n,k; double w; while(scanf("%d%lf%d",&n,&w,&k)!=EOF) { n+=2; int temp=n/k; n-=temp; printf("%.2lf\n",1.0*n*w/2+0.00499); } return 0; }