Noip模拟题解题报告

Pro

题目链接

一套暴力都无从下手的题目

Sol

susume

结论:在最优的方案中,青蛙一定会在某次恰好跳到一个禁区的右端点。

错解:二分……

因为是SPJ,没法测我自己的程序,不过0.2s的时限也是真的够了……挂标程

//#line 7 "TheFrog.cpp"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <climits>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <map>
#include <queue>
#include <utility>
#include <ctime>
#include <sstream>
using namespace std;

typedef long long ll;
#define pair(x, y) make_pair(x, y)

#define N 50

class TheFrog {
public:
	static const double eps = 1e-9;
	
	pair<double, double> p[N + 1];
	int n, D_;
	double ans, m;
	
	inline bool check(double x) {
		for (int i = 1; i <= n; ++i) {
			int ds = (int)(p[i].first / x + eps);
			if (x * (double)(ds + 1) < p[i].second - eps)
				return false;
		}
		return true;
	}
	
	double getMinimum(int D, vector <int> L, vector <int> R) {
		ans = 1e10, m = 1e10, D_ = D;
		n = L.size();
		for (int i = 1; i <= n; ++i) p[i] = pair((double)L[i - 1], (double)R[i - 1]);
		for (int i = 0; i < n; ++i) m = min(m, (double)R[i] - L[i]);
		sort(p + 1, p + n + 1);
		for (int i = 1; i <= n; ++i) {
			double x = p[i].second;
			int div = (int)(x / ans);
			while (x / (double)div > m - eps) {
				if (check(x / (double)div)) ans = min(ans, x / (double)div);
				++div;
			}
		}
		return ans;
	}
};

int n, D;
vector <int> L, R;

int main() {
	freopen("susume.in", "r", stdin);
	freopen("susume.out", "w", stdout);
	
	TheFrog solution;
	scanf("%d%d", &n, &D);
	for (int i = 1; i <= n; ++i) {
		int l, r;
		scanf("%d%d", &l, &r);
		L.push_back(l), R.push_back(r);
	}
	
	double ans = solution.getMinimum(D, L, R);
	printf("%.10lf\n", ans);
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

setdiff

又考了组合数……

枚举所有子集进行统计可以拿到30分。

最大值和最小值没有关系,可以分开计算。下面的分析中只考虑最大值。

考虑枚举成为最大值的元素,此时可以与这个元素在同一个集合里的应该是小于它的其他元素。

对于等于它的其它元素,为了避免重复计算,我们认为只有位置在其左侧的可以加入。

假设此时有个元素可以加入,那么方案数就是 C i − 1 k − 1 C_{i-1}^{k-1} Ci1k1,因为这个元素本身必须加入。

那包含这个元素的所有集合对答案的贡献为:(元素值) × C i − 1 k − 1 ×C_{i-1}^{k-1} ×Ci1k1

朴素的做法可以达到的复杂度 O ( n 2 ) O(n^2) O(n2),此时可以用Pascal公式处理组合数。可以拿到60分。

注意到,原始序列(序列)的顺序与答案无关。因此先对序列a排序。

再考虑成为最大值的元素。此时能够加入的元素即为其左侧的所有元素。

此时的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。因此考虑用预处理阶乘和其逆元的方法求组合数。可以拿到100分。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

long long n , k , mod , num[100005] , jc[100005] , ans;

inline long long mypow(long long a , long long b) {
	if(!b)
		return 1;
	long long t = mypow(a , b>>1)%mod;
	if(b&1)
		return t*t%mod*a%mod;
	return t*t%mod;
}

inline long long inv(long long x) {
	return mypow(x , mod-2)%mod;
}

inline void init() {
	jc[1] = 1;
	for(int i=2; i<=n; i++)
		jc[i] = jc[i-1]*i%mod;
}

inline long long C(long long x , long long y) {
	if(x<y)
		return 0;
	if(x==y)
		return 1;
	return jc[x]*inv(jc[y])%mod*inv(jc[x-y])%mod;
}

bool cmp1(long long a , long long b) { return a<b; }
bool cmp2(long long a , long long b) { return a>b; }

int main() {
	freopen("setdiff.in","r",stdin);
	freopen("setdiff.out","w",stdout); 
	scanf("%lld%lld%lld",&n,&k,&mod);
	for(int i=1; i<=n; i++)
		scanf("%lld",&num[i]);
	init();
	sort(num+1 , num+n+1 , cmp1);
	for(int i=k; i<=n; i++)
		ans = (ans + C(i-1,k-1)*num[i]%mod)%mod;
	sort(num+1 , num+n+1 , cmp2);
	for(int i=k; i<=n; i++)
		ans = (ans - C(i-1,k-1)*num[i]%mod + mod)%mod;
	printf("%lld",ans);
	return 0;
} 

swap

序列DP。

f[i][0/1]表示第i个元素是否与前一个元素发生交换。

根据题目规则我们可以知道,如果一段区间内所有的数字都发生交换,等价于这段区间的左端点到这段区间的右端点的位置,区间内其他区域统一左移。

通过这个不难写出方程,关键是转移顺序。

枚举发生交换的区间的右端点,从左往右扫一遍的话会比较麻烦,所以采取从右往左扫。

#include<iostream>
#include<cstdio>
#define INF 2147483647
using namespace std;

int n , num[2005] , f[2005][3] , sum[2005];
inline int mymin(int a , int b) { return a<b?a:b; }
inline int myabs(int a) { return a>0?a:(-a); }

int main() {
	freopen("swap.in","r",stdin);
	freopen("swap.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",&num[i]);
		if(i>1)
			sum[i] = sum[i-1] + myabs(num[i]-num[i-1]);
	}
	num[n+1] = num[n];
	for(int i=n-1; i>=1; i--) {
		f[i][0] = mymin(f[i+1][0]+myabs(num[i]-num[i+1]),f[i+1][1]+myabs(num[i]-num[i+2]));
		f[i][1] = INF;
		for(int j=i+1; j<n; j++)
			f[i][1] = mymin(f[i][1],mymin(f[j+1][0]+myabs(num[i]-num[j+1]),f[j+1][1]+myabs(num[i]-num[j+2]))+sum[j]-sum[i+1]+myabs(num[i]-num[j]));
		f[i][1] =  mymin(f[i][1] , sum[n]-sum[i+1]+myabs(num[n]-num[i]));
	}
	printf("%d",mymin(f[1][0],f[1][1]));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值