ICPC模拟选拔 Greater New York Regional 2009 (补题)

ICPC模拟选拔 Greater New York Regional 2009 (补题)

该题解参考并转自https://blog.csdn.net/u013050857/article/details/45080467

C:poj3783

题意:
  给定 B (B <= 50) 个一样的球,从 M (M <= 1000) 层楼上一个一个往下扔,存在某个楼层 K ,使得低于它的楼层往下扔球,球不会碎,在第 K 层扔下去会碎。求最坏情况下,需要扔几次才能确定这个 K 。


D : poj3748

题意:

  给一个序列,然后每到奇数项的时候就输出前面的数的中位数

思路:

  1、可以选择暴力,用vector和sort暴力跑一遍,比赛完看别人代码发现这样确实是可行的,我粗略计算了一下,大致是O( n2 logn)的复杂度,具体还是看sort会不会退化成O(n).

  2、正解:使用树状数组 + 二分进行计算,我们再开一个数组,复制的是原来输入的数字,然后从小到大排序,然后枚举原数组的每一个数字,将对应下标插入到树状数组中,这样就相当于边插入边排序,也就是说,当我枚举并插入当前数字时,一定是将它以及它之前的数字排好序了,并且那个排好序的数组就充当索引,然后当到奇数项时,我们再利用树状数组求和的性质,二分求出1-k的k保证前k项的和为 i / 2 + 1,那么就说明k这个数是中位数 时间复杂度是O(2 * n * logn).

代码:

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e4 + 5;

int a[maxn],tr[maxn],te[maxn];
int n,cas;

int lowbit(int x)
{
	return x & (-x);
}

void add(int x,int val)
{
	for(int i = x; i <= n; i += lowbit(i)) tr[i] += val;
}

int query(int x)
{
	int res = 0;
	for(int i = x; i > 0; i -= lowbit(i)) res += tr[i];
	return res;
}

int Find(int x)
{
	int l = 1,r = n,res;
	while(l <= r){
		int mid = (l + r) >> 1;
		if(te[mid] >= x){
			r = mid - 1;
			res = mid;
		}
		else l  = mid + 1;
	}
	return res;
}

int main()
{
	std::ios::sync_with_stdio(false);
	int t;
	cin>>t;
	while(t--){
		cin>>cas>>n;
		cout<<cas<<' '<<n / 2 + 1<<endl;
		memset(a,0,sizeof 0);
		memset(tr,0,sizeof tr);
		for(int i = 1; i <= n; i++){
			cin>>a[i];
			te[i] = a[i];
		} 
		sort(te + 1,te + n + 1);
		int count = 0,tot = n / 2 + 1,pos;
		for(int i  = 1; i <= n; i++){
			pos = Find(a[i]);
			//相当于用树状数组维护当前排列好的数字
			add(pos,1);
			if(i  % 2){
				count++;
				int l =  1, r  = n,num = i / 2 + 1,pos;
				while(l <= r){
					int mid = (l + r)>>1;
					if(query(mid) >= num){
						pos = mid;
						r  = mid -1;
					}
					else l = mid  + 1;
				}
				cout<<te[pos];
				if(count % 10 == 0) cout<<endl;
				else if(count != tot) cout<<' ';
			}
		}
		cout<<endl;
	}
	return 0;
}

F :

poj3786

题意:

 求长度为n的二进制整数中,相邻两个1的对数有k对(可重复使用)的整数个数,题目描述的很清楚了

思路:

  令长度为n,相邻1的对数为k的数的个数为DP[n][k],其中以0结尾的为DP[n][k][0],以1结尾的为DP[n][k][1],那么 DP[n][k] = DP[n][k][0] + DP[n][k][1];
并且有如下状态转移方程:

  1. 长度为n-1的二进制数在末尾加上一个0,相邻1的对数不变,所以有:
    DP[n][k][0] = DP[n-1][k][0] + DP[n-1][k][1];
  2. 长度为n-1的二进制数在末尾加上一个1,相邻1的对数取决于,第n-1位是0还是1,当第n-1位是1,相邻1的对数+1;当第n-1位是0,相邻1的对数不变,所以有:
    DP[n][k][1] = DP[n-1][k][0] + DP[n-1][k-1][1];
    并且初始状态下DP[1][0][0] = DP[1][0][1] = 1//也各算一种情况

代码

/*
dp三维数组,dp[i][j][k]:n,k,(0,1)
n位数,值位k,末尾为(0,1)
状态转移方程:
  dp[i][j][0]+=(dp[i-1][j][0]+dp[i-1][j][1]);
  dp[i][j][1]+=(dp[i-1][j][0]+dp[i-1][j-1][1]);
*/

#include <iostream>
#include <cstring>

using namespace std;

typedef long long  ll;
//typedef __int128 LL;

const int maxn = 1000 + 5;
const int Mod = 998244353;

int dp[maxn][maxn][2];

int Dp(int n, int k)
{
    memset(dp,0,sizeof dp);
    dp[1][0][0]  = dp[1][0][1] = 1;
    for(int i  = 1; i <= n; i++){
        for(int j = 0; j <= k; j++){
            dp[i][j][0] += dp[i - 1][j][0] + dp[i - 1][j][1];
            dp[i][j][1] += dp[i - 1][j][0] + dp[i - 1][j - 1][1];
        }
    }
    return dp[n][k][0] + dp[n][k][1];
}

int main()
{
    int t;
    cin>>t;
    while(t--){
        int cas,n,k;
        cin>>cas>>n>>k;
        int res = Dp(n,k);
        cout<<cas<<' '<<res<<endl;
    }
    return 0;
}



Guess you like

Origin blog.csdn.net/CUCUC1/article/details/109277928