递归练习1
感觉自己这方面很弱,都是看着题解做的orz..
fzu2038 Another Postman Problem(递归求解)
题意:n个点n-1条边组成无向连通图,求每个点到其他所有点的路径总和的和。
题解:每条边的访问次数为边两端点数乘积的两倍。递归遍历每个点的每条边即可。
1 #include<cstdio> 2 #include<vector> 3 #include<cstring> 4 using namespace std; 5 const int N=1e5+5; 6 typedef long long ll; 7 int n; 8 ll sum; 9 struct edge{ 10 int v,c; 11 edge(int v,int c):v(v),c(c){} 12 }; 13 vector<edge>g[N]; 14 int vis[N]; 15 ll dfs(int u){ 16 vis[u]=1; 17 ll cnt=1; 18 for(int i=0;i<g[u].size();++i){ 19 int v=g[u][i].v; 20 if(vis[v])continue; 21 ll t=dfs(v); 22 sum+=2*g[u][i].c*t*(n-t); 23 cnt+=t; 24 } 25 return cnt; 26 } 27 int main(){ 28 int i,t,k,a,b,c; 29 scanf("%d",&t); 30 for(k=1;k<=t;++k){ 31 scanf("%d",&n); 32 for(i=0;i<n;++i)g[i].clear(); 33 memset(vis,0,sizeof(vis)); 34 for(i=0;i<n-1;++i){ 35 scanf("%d%d%d",&a,&b,&c); 36 g[a].push_back(edge(b,c)); 37 g[b].push_back(edge(a,c)); 38 } 39 sum=0; 40 dfs(0); 41 printf("Case %d: %I64d\n",k,sum); 42 } 43 return 0; 44 }
hdu5355 Cake(回溯)
题意:有n个尺寸大小分别为1,2,3..,n的蛋糕,问能否将之平均分成m份(不能再切割),能的话输出每份蛋糕数量及尺寸。
题解:无解情况有两种:
①所有蛋糕大小之和不能被m整除;
②每一份蛋糕大小之和小于蛋糕大小最大值n,转化即n<2m-1。
其他情况有解。一直用2m去减n,直至n小于40(不知道为什么是40(>_<))然后回溯法求40以内能划分的情况。
官方题解:
1 #include<cstdio> 2 #include<algorithm> 3 #include<vector> 4 using namespace std; 5 const int N=41; 6 int a[N],id[N],n,m; 7 vector<int>g[N]; 8 int dfs(int n,int k){ 9 if(n<1)return 1; 10 for(int i=1;i<=m;++i){ 11 if(a[i]+n<=k){ 12 id[n]=i; 13 a[i]+=n; 14 if(dfs(n-1,k))return 1; 15 id[n]=0; 16 a[i]-=n; 17 } 18 } 19 return 0; 20 } 21 void solve(int n){ 22 int i,j; 23 for(i=1;i<=m;++i){ 24 g[i].clear(); 25 a[i]=id[i]=0; 26 } 27 while(n>40){ 28 for(i=1;i<=m;++i) g[i].push_back(n--); 29 for(i=m;i>=1;i--) g[i].push_back(n--); 30 } 31 dfs(n,n*(n+1)/2/m); 32 for(i=1;i<=n;++i) g[id[i]].push_back(i); 33 for(i=1;i<=m;++i){ 34 sort(g[i].begin(),g[i].end()); 35 int len=g[i].size(); 36 printf("%d",len); 37 for(j=0;j<len;++j) printf(" %d",g[i][j]); 38 printf("\n"); 39 } 40 } 41 int main(){ 42 int t; 43 scanf("%d",&t); 44 while(t--){ 45 scanf("%d%d",&n,&m); 46 if((1LL*n*(n+1)/2)%m||n<2*m-1) printf("NO\n"); 47 else{ 48 printf("YES\n"); 49 solve(n); 50 } 51 } 52 return 0; 53 }
hdu5225 Tom and permutation(回溯)
题意:给出一个n的排列,求所有字典序小于给定排列的逆序对数之和。
题解:预处理1~N所有全排列的逆序数和。枚举第k位。dfs,每次考虑当前位(之前的考虑过),如果与原序列对应数不同,后面的逆序对数可得,排列数为阶乘,然后求当前位对后面产生的逆序对数;否则,将后面数对逆序对数的影响留到后面的dfs中改。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 typedef long long ll; 6 const int N=101; 7 const int mod=1e9+7; 8 ll s[N],num[N];//逆序数和,排列数 9 int vis[N],a[N],n,ans; 10 ll dfs(int k,int f){ 11 if(k>n) return 0; 12 if(f==0){ 13 ans=(ans+s[n-k+1])%mod; 14 return num[n-k+1]; 15 } 16 else{ 17 ll cnt=0; 18 for(int i=1;i<=a[k];++i){ 19 if(vis[i])continue; 20 vis[i]=1; 21 int s1=0,j; 22 for(j=1;j<i;++j) 23 if(vis[j]==0) s1++; 24 ll t=dfs(k+1,i==a[k]?1:0); 25 vis[i]=0; 26 ans=(ans+t*s1%mod)%mod; 27 cnt=(cnt+t)%mod; 28 } 29 return cnt; 30 } 31 } 32 int main(){ 33 int i; 34 s[1]=0; 35 num[1]=1; 36 for(i=2;i<N;++i){ 37 s[i]=s[i-1]*i%mod+num[i-1]*((1LL*i*(i-1)/2)%mod)%mod; 38 num[i]=num[i-1]*i%mod; 39 //printf("%lld %lld\n",s[i],num[i]); 40 } 41 while(scanf("%d",&n)==1){ 42 ans=0; 43 for(i=1;i<=n;++i) scanf("%d",&a[i]); 44 memset(vis,0,sizeof(vis)); 45 dfs(1,1); 46 printf("%d\n",ans); 47 } 48 return 0; 49 }
hdu4911 Inversion (归并排序)
题意:给出一个序列,可以交换相邻位置的数不超过k次,求交换后最小逆序数。
题解:用归并方法求原序列逆序数cnt,在归并过程中计算每个小区间的逆序对数,进而计算出大区间的逆序对数。因为交换一次可以减少一个逆序对数。所以最后答案为max(0,cnt-k)。
注意,归并排序是稳定的排序,即相等的元素的顺序不会改变,这是它比快速排序优势之处。
1 #include<cstdio> 2 long long cnt,k; 3 int n,a[100001],b[100001]; 4 void Merge(int l,int m,int r){ 5 int i=l,j=m+1,c=0; 6 while(i<=m||j<=r){ 7 if(a[i]<=a[j]&&i<=m||j>r) 8 b[c++]=a[i++]; 9 else{ 10 b[c++]=a[j++]; 11 cnt+=(m-i+1); 12 } 13 } 14 for(i=0;i<c;++i) 15 a[l+i]=b[i]; 16 } 17 void merge_sort(int l,int r){ 18 if(l<r){ 19 int m=(l+r)/2; 20 merge_sort(l,m); 21 merge_sort(m+1,r); 22 Merge(l,m,r); 23 } 24 } 25 int main(){ 26 while(scanf("%d%lld",&n,&k)==2){ 27 for(int i=0;i<n;++i) scanf("%d",&a[i]); 28 cnt=0; 29 merge_sort(0,n-1); 30 if(k>=cnt)printf("0\n"); 31 else printf("%lld\n",cnt-k); 32 } 33 return 0; 34 }
hdu5323 Solve this interesting problem(dfs)
题意:求最小的线段树的右端点,使得给定的区间[L,R]是某节点
题解:注意题目的数据,L/(R-L+1)<=2015,向上搜时,L不变,(R-L+1)翻倍,所以L/(R-L+1)每上一层变为原来的1/2,所以层数最多只有log22015=11,不难想到dfs暴力扩展。
从[L,R]向根部搜,根据(L+R)的奇偶性判断其父区间有四种可能:[L,2R-L]、[2(L-1)-R,R]、[L,2R-L+1]、[2(L-1)-R+1,R]。其中父节点为[L,2R-L]时,必须R>L,否则它右儿子为空,就矛盾了。
注意到无解条件:L<(R-L+1),因为左儿子不可能比右儿子表示的区间长度小。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 typedef long long ll; 5 const ll inf=0x3f3f3f3f; 6 ll L,R,n; 7 void dfs(ll l,ll r){ 8 if(l==0||r>=2*R){ 9 if(l==0&&n>r)n=r; 10 return; 11 } 12 int c=r-l+1; 13 if(l<c)return; 14 dfs(l-c,r); 15 dfs(l-c-1,r); 16 dfs(l,r+c); 17 if(c>1) dfs(l,r+c-1); 18 } 19 int main(){ 20 while(scanf("%lld%lld",&L,&R)==2){ 21 n=inf; 22 dfs(L,R); 23 printf("%lld\n",n==inf?-1:n); 24 } 25 }