2018-08-04 训练4

  • A  -- Tri Tiling

  • Description

In how many ways can you tile a 3xn rectangle with 2x1 dominoes?
Here is a sample tiling of a 3x12 rectangle.

  • Input

Input consists of several test cases followed by a line containing -1. Each test case is a line containing an integer 0 <= n <= 30

  • Output

For each test case, output one integer number giving the number of possible tilings

  • Sample Input

2

8

12

-1

  • Sample Output

3

153

2131

  • 题目理解

很容易考虑出来当n为奇数的时候,不可能铺满所有的面积,所以种类为0;

这样的题目大部分是通过递推出来后面的结果,这时候我们思考当f(2)f(4)...f(n-2)已经得出的时候来求f(n).对于f(n-2)来说要变成f(n)水平方向增加两个单位,有三种铺法。当f(n-4)转变为f(n)要增加四个单位,这里如果是2+2模式已经包含在f(n-2)的可能中,所以应该在2|2分界线的地方放置瓷砖,由此可以增添两种可能,以此类推,之前更加小的f都只带来两种可能因为2| ... |2在分界线需要放置瓷砖

得到转移方程f(n)=3*f(n-2)+2*(f(n-4)+f(n-6)+...+f(4)+f(2))

这里需要注意的是a[0]=1,不赋值会WA掉

#include<cstdio>
int main()
{
      int a[32]={0},x;
      a[0]=1;
      a[2]=3;
      a[4]=11;
      for(int i=6;i<=30;i=i+2){
             a[i]=4*a[i-2]-a[i-4];
      }
      while(scanf("%d",&x)!=EOF&&x!=-1){
             printf("%d\n",a[x]);
      }
      return 0 ;
}

 

  • B  -- Maximum sum

  • Description

题目链接:http://poj.org/problem?id=2479

  • Input

The input consists of T(<=30) test cases. The number of test cases (T) is given in the first line of the input.
Each test case contains two lines. The first line is an integer n(2<=n<=50000). The second line contains n integers: a1, a2, ..., an. (|ai| <= 10000).There is an empty line after each case

  • Output

Print exactly one line for each test case. The line should contain the integer d(A)

  • Sample Input

1

10

1 -1 2 2 3 -3 4 -4 5 -5

  • Sample Output

13

  • 题目理解

这道题需要有两段区间相加,可以不连续。定义dp(i)为以a_i结尾的大连续和。首先需要判断dp(i-1)是否为负,如果为负则前面全不选只记录当前元素作为sum,前面使用一个tmp变量记录前面最大的dp值然后选取最大值;如果为正,当前的值需要累加到sum值中再去与tmp比较,这时候dp(i)取前面的最大值;从后往前相同方法求前i个元素的最大连续和。然后枚举中间断点保证两区间不相交。枚举得到最大值

#include<cstdio>
#include<climits>
#include<algorithm>
using namespace std;
const int maxn=50005;
int dp[maxn],num[maxn];
int main(){
	int t,n,tmp,sum,ans;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		tmp=INT_MIN;
		sum=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&num[i]);
			sum+=num[i];
			tmp=max(tmp,sum);
			dp[i]=tmp;
			if(sum<0) sum=0;//前面为负舍去前面的部分
		}
        tmp=ans=INT_MIN;
        sum=0;
        for(int i=n;i>1;i--)
        {
            sum+=num[i];
            tmp=max(tmp,sum);
            ans=max(ans,dp[i-1]+tmp);
            if(sum<0) sum = 0;
        }
        printf("%d\n",ans);
	}
	return 0;
}

 

  • C  --  不堪重负的树

  • Description

小X非常喜欢树,然后他生成了一个大森林给自己玩。
玩着玩着,小X陷入了沉思。

  • 一棵树由N个节点组成,编号为i的节点有一个价值Wi。
  • 假设从树根出发前往第i个节点(可能是树根自己),一共需要经过Di个节点(包括起点和终点),那么这个节点对这棵树产生的负担就是Di与Wi的乘积。
  • 对于一棵树而言,这棵树的负担值为所有节点对它产生的负担之和。

小X学习了dfs,如果他知道树的结构,他当然可以很容易地算出树的负担值。可是现在沉思中的小X并不知道树的结构形态,他只知道一棵二叉树的中序遍历以及每个节点的价值,那么这棵二叉树可能的最小负担值是多少呢?

  • Input

第一行为一个正整数T(T≤20)表示数据组数。
每组数据包括三行。
第一行为一个正整数N(N≤200)。
第二行为N个正整数Wi(Wi≤108),表示编号为i的节点的价值。
第三行为N个正整数Pi(Pi≤N),为一个1~N的排列,表示二叉树的中序遍历结果

  • Output

对于每组数据,输出一行一个正整数,表示这棵树可能的最小负担值

  • Sample Input

2

4

1 2 3 4

1 2 3 4

7

1 1 1 1 1 1 1

4 2 3 5 7 1 6

  • Sample Output

18

17

  • 题目理解

中序遍历所有结点的情况都不能确定但是我们知道根一定在中间,左区间为左子树,右区间为右子树;emmm很想区间dp,于是我么定义dp(i,j)i-j结点组成的树最小负担,这时候很容易得到dp(i,j)=dp(i,k-1)+dp(k+1,j)+sum[j]-sum[i-1]其中(i<k<j)后面的连续和所增加的负担是因为,当两棵子树组成一棵树的时候所有结点相当于平行下移一个深度这时候加上根节点增加的负担就是i-j所有结点负担之和

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=205;
ll a[maxn],sum[maxn],dp[maxn][maxn];
int main()
{
    int t,n,leaf;
    while(scanf("%d",&t)!=EOF){
        while(t--){
            memset(dp,0,sizeof(dp));
            sum[0]=0;
            scanf("%d",&n);
            for(int i=1;i<=n;++i)
                scanf("%lld",&a[i]);
            for(int i=1;i<=n;++i){
                scanf("%d",&leaf);
                sum[i]=sum[i-1]+a[leaf];
                dp[i][i]=a[leaf];//作为叶子需要初始化的值
            }
            for(int j=2;j<=n;++j){
                for(int i=j-1;i>=1;--i){
                    dp[i][j]=min(dp[i+1][j],dp[i][j-1])+sum[j]-sum[i-1];
                    for(int k=i+1;k<j;++k)
                        dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+sum[j]-sum[i-1]);
                }
            }
            printf("%lld\n",dp[1][n]);
        }
    }
    return 0;
}

 

  • D  -- Balancing Act

  • Description

Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. Deleting any node from the tree yields a forest: a collection of one or more trees. Define the balance of a node to be the size of the largest tree in the forest T created by deleting that node from T.
For example, consider the tree:


Deleting node 4 yields two trees whose member nodes are {5} and {1,2,3,6,7}. The larger of these two trees has five nodes, thus the balance of node 4 is five. Deleting node 1 yields a forest of three trees of equal size: {2,6}, {3,7}, and {4,5}. Each of these trees has two nodes, so the balance of node 1 is two.

For each input tree, calculate the node that has the minimum balance. If multiple nodes have equal balance, output the one with the lowest number.

  • Input

The first line of input contains a single integer t (1 <= t <= 20), the number of test cases. The first line of each test case contains an integer N (1 <= N <= 20,000), the number of congruence. The next N-1 lines each contains two space-separated node numbers that are the endpoints of an edge in the tree. No edge will be listed twice, and all edges will be listed

  • Output

For each test case, print a line containing two integers, the number of the node with minimum balance and the balance of that node

  • Sample Input

1

7

2 6

1 2

1 4

4 5

3 7

3 1

  • Sample Output

1 2

  • 题目理解

通过dfs计算所有儿子作为根节点所形成的树的所有节点数,然后其父亲结点cnt[father]=n-1-\sum cnt[son]计算得到,在退栈的过程中使用dp记录最大值,结束dfs再查找所有dp值中最小值以及结点输出即可

#include<cstdio>
#include<vector>
#include<cstring>
#include<climits>
#include<algorithm>
using namespace std;
const int maxn=20005;
struct ver{
   int v;
   ver(int V):v(V){}
};
vector<ver> edge[maxn];
int dp[maxn],cnt[maxn],n;
void dfs(int father,int son){
    int v;
    for(int i=0;i<edge[son].size();++i){
       v=edge[son][i].v;
       if(v==father) continue;
       dfs(son,v);
       cnt[son]+=cnt[v];
    }
    dp[son]=n-1-cnt[son];
    for(int i=0;i<edge[son].size();++i){
       v=edge[son][i].v;
       if(v==father) continue;
       dp[son]=max(dp[son],cnt[v]);
    }
    cnt[son]++;
}
int main()
{
    int t,u,v;
    while(scanf("%d",&t)!=EOF){
        while(t--){
            for(int i=0;i<maxn;++i) edge[i].clear();
            memset(cnt,0,sizeof(cnt));
            scanf("%d",&n);
            for(int i=1;i<n;++i){
                scanf("%d%d",&u,&v);
                edge[u].push_back(v);
                edge[v].push_back(u);
            }
            dfs(0,1);
            int ans=INT_MAX,pos=0;
            for(int i=1;i<=n;++i){
                if(dp[i]<ans){
                    ans=dp[i];
                    pos=i;
                }
            }
            printf("%d %d\n",pos,ans);
        }
    }
    return 0;
}

 

  • E  -- Number Sequence

  • Description

A single positive integer i is given. Write a program to find the digit located in the position i in the sequence of number groups S1S2...Sk. Each group Sk consists of a sequence of positive integer numbers ranging from 1 to k, written one after another.
For example, the first 80 digits of the sequence are as follows:
11212312341234512345612345671234567812345678912345678910123456789101112345678910

  • Input

The first line of the input file contains a single integer t (1 ≤ t ≤ 10), the number of test cases, followed by one line for each test case. The line for a test case contains the single integer i (1 ≤ i ≤ 2147483647)

  • Output

There should be one output line per test case containing the digit located in the position i

  • Sample Input

2

8

3

  • Sample Output

2

2

  • 题目理解

和之前做过的一道递推题十分相似,首先通过规律记录增加的区间长度,通过nsum[i]比较得出n所在的S_k。然后通过对S_k分割成S^{'}_1S^{'}_{12}S^{'}_{123} ... 将数字段的最后一位的坐标以及数字段求出来,通过最后一位与n相比较得出后面多余的位数,然后取模就可以输出

#include <cstdio>
#include <cmath>
const int maxn=31269;
long long num[maxn], sum[maxn];
void init()
{
    num[1]=sum[1]=1;
    for(int i=2;i<maxn;i++)
    {
        num[i] = num[i-1]+(int)log10((double)i)+1;
        sum[i] = sum[i-1]+num[i];
    }
}
int solve(int n)
{
    int i=0,pos,cnt;
    while(sum[i]<n)  i++;
    pos=n-sum[i-1];
    cnt=0;
    for(i=1;cnt<pos;i++)
        cnt+=(int)log10((double)i)+1;
    return (i-1)/(int)pow((double)10.0,(cnt-pos))%10;
}
int main()
{
    int t, n;
    init();
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        printf("%d\n", solve(n));
    }
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值