2013 Multi-University Training Contest 1

/*
B:Partition
AC 31MS 304K 729 B C++ 
题意:给你一个n,n可以被分解为一个或多个正整数的和,一共会有2^(n-1)种分解方法,再给你一个k,问在这么多分解方法里,出现k的次数。
分析:列一下4,5的分解方法,发现5的分解方法中2出现的次数等于4中1出现的次数,推证n中k出现的次数等于n-k+1中1出现的次数,那么有价值的数字就是n-k+1;
   就只需要求出n,1的输出即可,找规律啦~找到递推公式(n>2时)An=2*A(n-1)+2^(n-2);然后两边同时除以2^n,得到通项公式(n>2时)An = 2^(n-2)*(n+3);
   n<=2时打表即可。求2的幂次时用快速幂即可。
另:题解上这样写道:
 我们可以特判出 n <= k的情况。
 对于 1 <= k < n,我们可以等效为 n个点 排成一列,并取出其中的连续k个点 。
 下面分两种情况考虑:
 第一种情况,被选出的不包含端点那么有(n–k−1)种情况完成上述操作,剩下未被圈的点之间还有(n–k−2)个位置,可以在每个位置断开,所以共2^(n−k−2) ∗ (n−k−1)种方法。
 第二种情况,即被选出的包含端点,那么有2种情况,并且剩余共(n–k−1)个位置,所以共2 ∗ 2^(n–k−1)种方法。 
 总计 2 ∗ 2^( n – k − 1) + 2^( n – k − 2) ∗ (n – k − 1) = (n – k + 3) * 2^( n – k − 2)。
 题解的思想才是真快,不知何时思维才能够如此敏捷o(╯□╰)o
注意:分情况的时候,先处理一下k>n的情况,输出0;
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 1000000007
#define ll __int64
int a[4];
void init()
{
       a[0] = 0;a[1] = 1;a[2] = 2;a[3] = 5;
}
int main()
{
  init();
        ll m,n;
  int t;cin>>t;
  ll maxe;
        while(t--){
   scanf("%I64d%I64d",&m,&n);   
     maxe = m-n+1;
     if(maxe<=3){
      printf("%d\n",a[maxe]);
      continue;
     }else{
     maxe -= 3;
     ll tmp = 1,x=2 ;
     while(maxe>0){//快速幂
      if(maxe%2 == 1)
      {
       tmp = tmp*x;
       tmp%=inf;
      }
     maxe/=2;
     x *=x;
     x%=inf;      
     }
     tmp = tmp*(m-n+3)%inf;//通项公式
     printf("%I64d\n",tmp);
     } 
  }
  return 0;
}
/*
E:Deque
AC	328MS	2680K	1265B	C++
题意:按顺序给你一列数,一个一个的,你可以依次将它插到一个deque里,方法是可以place到头或尾,也可以先pop掉头或尾的元素,再进行place操作,最后使deque(不下降)的长度最长
分析:对每一个数进行枚举,分别记录按顺序place后,包含该数的最长不下降和最长不上升子序列的长度len1,len2,另外也需要记录该数的重复次数same。结果就是max(len1+len2-same);
*/
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
#define M 100000+5
int dp1[M],dp2[M];
int same[M];
int n,o[M];
void up()
{
	vector<int >v;//story the lenth of the current longest subsquences
	int a,b;
	for (int i = 0; i < n; i++)
	{
		int now = o[i];
		a = lower_bound(v.begin(),v.end(),now)-v.begin();//找到大于等于now的第一个位置的下标
		b = upper_bound(v.begin(),v.end(),now)-v.begin();//找到比now大的第一个位置的下标
		//不断更新v,取最优情况。
		if(b == v.size()){
			v.push_back(now);
		}else{
			v[b] = now;
		}
		dp1[i] = b+1;//记录包含该数的最长不下降子序列的长度,等于b+1;
		same[i] = b-a+1;
	}
	return;
}
void down()
{
	vector<int >v;//story the lenth of the current longest subsquences
	int a,b;
	for (int i = 0; i < n; i++)
	{
		int now = o[i];
		a = lower_bound(v.begin(),v.end(),now)-v.begin();
		b = upper_bound(v.begin(),v.end(),now)-v.begin();
		if(b == v.size()){//
			v.push_back(now);
		}else{
			v[b] = now;
		}
		dp2[i] = b+1;
		same[i] = min(same[i],b-a+1);
	}
	return;
}
int main()
{
	int t;
	cin>>t;
	while(t--){
		cin>>n;
		for (int i = n-1; i >= 0; i--)
			scanf("%d",&o[i]);
		up();
		for(int i = 0;i<n;i++)
			o[i] = -o[i];//求下降的数组 == 原数组取相反数求上升的数组
		down();
		int ans = 0;
		for (int i = 0; i < n; i++)
		{
			ans = max(ans,dp1[i]+dp2[i]-same[i]);
		}
		cout<<ans<<endl;
	}
	return 0;
}

 

顺便说一下上题里用到的lower_bound和upper_bound函数:

函数upper_bound()返回的在前闭后开区间查找的关键字的上界,如一个数组number序列1,2,2,4.upper_bound(2)后,返回的位置是3(下标)也就是4所在的位置,同样,如果插入元素大于数组中全部元素,返回的是last。(注意:此时数组下标越界!!)

返回查找元素的最后一个可安插位置,也就是“元素值>查找值”的第一个元素的位置。

函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置

举例如下:

一个数组number序列为:4,10,11,30,69,70,96,100.设要插入数字3,9,111.pos为要插入的位置的下标

pos = lower_bound( number, number + 8, 3) - number,pos = 0.即number数组的下标为0的位置。

pos = lower_bound( number, number + 8, 9) - number, pos = 1,即number数组的下标为1的位置(即10所在的位置)。
pos = lower_bound( number, number + 8, 111) - number, pos = 8,即number数组的下标为8的位置(但下标上限为7,所以返回最后一个元素的下一个元素)。
所以,要记住:函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置,且last的位置是越界的!!~

返回查找元素的第一个可安插位置,也就是“元素值>=查找值”的第一个元素的位置。


 

/*
I:I-Number
AC	312MS	400K	955 B	C++	
题意:给你一个正整数x,要求你输出一个数y,要满足两个要求:y>x,y的所有数位上的数的和是10的倍数。
分析:计算出x所有数位的和mod10等于m,两种情况:
	  1:if m!=0,而且m加到x的个位上也不产生进位,就直接加上输出。
	  2:m==0或会产生进位,把个位清零,向前进位,一直进,然后得到一个个位是0的数,再计算m,然后个位上加上10-m即可。
注意:用char数组做,9999....的情况要加一位。
另:本题有个小问题,如果输入带前导零的数字,应该输出时把前导零处理掉,但hdu保留了前导零,大家注意下。
*/
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
char a[100005];
int main()
{
	int t;
	cin>>t;
	int sum,last,cha,lcha;
	while(t--)
	{
		sum = 0;
		cin>>a; 
		int len = strlen(a)-1;
		for (int i = 0; a[i] != '\0'; i++)
		{
			sum += (a[i]-'0')%10;
			sum %=10;
			if(a[i+1] == '\0')
				last = a[i]-'0';
		}
		cha = 10-sum;
		if(cha!=10 && cha+last<10){
			a[len] += cha;
			cout<<a<<endl;
		}else{
			int j = len;
			if(j == 0){
				cout<<"19"<<endl;
				continue;
			}
			a[j] = '0';
			a[j-1] += 1;
			--j;
			while(a[j]-'0'>=10 && j>0)
			{
				a[j] = '0';
				a[--j]+=1;
			}
			if(j==0 && a[0]-'0' == 10)
			{
				a[0] = '0';
				a[len] = '9';
				cout<<"1"<<a<<endl;
			}else{
				sum = 0;
				for (int i = 0; a[i] != '\0'; i++)
				{
					sum += (a[i]-'0')%10;
					sum%=10;
				}
				if(sum!=0)				a[len] = 10-sum+'0';
				cout<<a<<endl;
			}
		}
	}
	return 0;
}

/*
H:Park Visit
AC	640MS	4468K	1113 B	
题意:给你一个有向无环图,其中每条边的权值都是1,问你要visit其中k个点,至少需要花费多少?
分析:求出树的直径d(两遍dfs),如果d<=k输出k-1,否则k-1+2*(k-d);
PS: 不知道为啥bfs超时。。。
*/
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
#define M 110000+5
vector<int >map[M];
int n;
int d[M];
void init()
{
	for (int i = 1; i <= n; i++)
			map[i].clear();
}
void  dfs(int x,int fa,int dp)//多一个参数fa,可以减少对d数组的初始化,可以优化好多时间。
{
	d[x] = dp;
	for (int i = 0; i < map[x].size(); i++)
		if(map[x][i] != fa)
			dfs(map[x][i],x,dp+1);
}
int main()
{
	//FILE *p;
	//freopen_s(&p,"1008.in","r",stdin);
	//freopen_s(&p,"out.txt","w",stdout);
	int t,k,D,Q;
	cin>>t;
	while(t--)
	{
		scanf("%d%d",&n,&Q);
		init();
		int u,v;
		for (int i = 1; i < n; i++)
		{
			scanf("%d%d",&u,&v);
			map[u].push_back(v);
			map[v].push_back(u);
		}
		int root = 1;
		dfs(root,0,0);
		for (int i = 2; i <= n; i++)
			if(d[i]>d[root])
				root = i;
		dfs(root,0,0);
		for (int i = 2; i <= n; i++)
			if(d[i]>d[root])
				root = i;
		D = d[root];
		while(Q--){
			scanf("%d",&k);
			if(k <= D+1)printf("%d\n",k-1);
			else printf("%d\n",D+(k-D-1)*2);
		}
	}
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值