NOIP2016 解题报告

D1T1 玩具谜题

xjb模拟即可

#include<bits/stdc++.h>
#define N (100000+5)
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c;
	c = getchar();
	while (!isdigit(c)) {
		if (c == '-') f = -f;
		c = getchar();
	}
	while (isdigit(c)) {
		cnt = (cnt << 3) + (cnt << 1) + c - '0';
		c = getchar();
	}
	return cnt * f;
}
int n, m;
struct node{
	int opr;
	char name[20];
} a[N];
int s, t, p;
int main() {
	n = read(); m = read();
	for (register int i = 1; i <= n; i++) {
		a[i].opr = read();
		scanf("%s
", a[i].name + 1);
	}
	p = 1;
	for (register int i = 1; i <= m; i++) {
		s = read(); t = read();
		if (s == a[p].opr) {
			p -= t;
			p += n;
			p %= n;
			if (p == 0) p = n;
		} else {
			p += t;
			p %= n;
			if (p == 0) p = n;
		}
	}
	for (register int i = 1; i <= strlen(a[p].name + 1); i++) putchar(a[p].name[i]);
	return 0;
}

D1T2 天天爱跑步

神仙题各色乱搞大杂烩集中营 我也不知道为什么它会出现在(D1T2) 单独写

D1T3 换教室

期望(dp),设(dp[i][j][0/1])表示考虑到第(i)门课,已经换了(j)门,这次换/不换的答案

状态转移时,按这次申请换/不换分别考虑,若这次不换则这次的选择一定是百分百不换,那么分别算一下上一次换没换成功对答案贡献即可

若这次换那么分开考虑上次换成功与否(这决定了我们用哪一个状态转移),若上次不换则有两种情况,这次成功/不成功;若上次换则有四种情况,即这次成功与否和上次成功与否乘法原理组合一下,对答案的贡献就是它们分别要走的路程分别乘上它们时间发生的概率

方程比较窒息,建议手推一下或直接看代码

[dp[i][j][0] = min left{ egin{aligned} &dp[i - 1][j][0] + Map[c[i - 1]][c[i]] \ &dp[i - 1][j][1] + Map[c[i - 1]][c[i]] * (1 - k[i - 1]) + Map[d[i - 1]][c[i]] * k end{aligned} ight. ]

[dp[i][j][1] = left{ egin{aligned} &dp[i - 1][j - 1][0] + Map[c[i - 1]][d[i ]] * k[i] + Map[c[i - 1]][c[i]] * (1 - k[i]) \ &dp[i - 1][j - 1][1] + Map[c[i - 1]][d[i]] * (1 - k[i - 1]) * k[i] + Map[d[i - 1]][d[i]] * k[i - 1] * k[i] + Map[c[i - 1]][c[i]] * (1 - k[i]) * (1 - k[i - 1]) + Map[d[i - 1]][c[i]] * k[i - 1] * (1 - k[i]) end{aligned} ight. ]

因为数据范围很小,教室之间距离用(Floyd)求出即可

#include<bits/stdc++.h>
#define N (2000 + 10)
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
	return cnt * f;
}
int n, m, v, e, a[N], b[N], Map[305][305], dis[305][305];   //2n-course, max_request = m, v = num_of_vertex, e = num_of_edge
double k[N], f[N][N][2], ans;
int x, y, w;
void get() {
	n = read(), m = read(), v = read(), e = read();
	for (register int i = 1; i <= v; ++i) 
		for (register int j = 1; j <= v; ++j) 
			dis[i][j] = Map[i][j] = 1e9;
	for (register int i = 1; i <= n; ++i) a[i] = read();
	for (register int i = 1; i <= n; ++i) b[i] = read();
	for (register int i = 1; i <= n; ++i) scanf("%lf", &k[i]);
	for (register int i = 1; i <= e; ++i) {
		x = read(), y = read(), w = read();
		dis[x][y] = dis[y][x] = Map[x][y] = Map[y][x] = min(Map[x][y], w);
	}
}
int main() {
	get();
	for (register int i = 1; i <= v; ++i) dis[i][i] = Map[i][i] = 0;
	for (register int p = 1; p <= v; ++p)
		for (register int i = 1; i <= v; ++i)
			for (register int j = 1; j <= v; ++j) dis[i][j] = min(dis[i][j], dis[i][p] + dis[p][j]);

	for (register int i = 0; i <= n; ++i) 
		for (register int j = 0; j <= m; ++j) f[i][j][0] = f[i][j][1] = 1e9;
	f[1][0][0] = f[1][1][1] = 0;
	for (register int i = 2; i <= n; ++i) f[i][0][0] = f[i - 1][0][0] + dis[a[i - 1]][a[i]];
	for (register int i = 2; i <= n; ++i)
		for (register int j = 1; j <= m; ++j) {
			f[i][j][0] = min(f[i][j][0], min(f[i - 1][j][0] + dis[a[i - 1]][a[i]], f[i - 1][j][1] + (1 - k[i - 1]) * dis[a[i - 1]][a[i]] + k[i - 1] * dis[b[i - 1]][a[i]]));
			f[i][j][1] = min(f[i][j][1],
				 min(f[i - 1][j - 1][0] + dis[a[i - 1]][a[i]] * (1 - k[i]) + dis[a[i - 1]][b[i]] * k[i], 
				 f[i - 1][j - 1][1] + dis[b[i - 1]][a[i]] * (1 - k[i]) * k[i - 1] + dis[b[i - 1]][b[i]] * k[i] * k[i - 1] + dis[a[i - 1]][a[i]] * (1 - k[i - 1]) * (1 - k[i]) + dis[a[i - 1]][b[i]] * (1 - k[i - 1]) * k[i])) ;
		}
	ans = 1e9;
	for (register int i = 0; i <= m; ++i) {
		if (f[n][i][0] < ans) ans = f[n][i][0];
		if (f[n][i][1] < ans) ans = f[n][i][1];
	}
	printf("%.2lf", ans);
	return 0;
}

D2T1 组合数问题

组合恒等式求组合数+二维前缀和统计答案

#include<bits/stdc++.h>
#define int long long
#define N (2000 + 10)
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
	return cnt * f; 
}
int n, m, k, T;
int c[N][N], ans[N][N];
void pre_work() {
	c[1][1] = c[0][0] = c[1][0] = 1;
	for (register int i = 2; i <= 2000; ++i) {
		c[i][0] = 1;
		for (register int j = 1; j <= i; ++j) {
			c[i][j] = (c[i - 1][j] % k + c[i - 1][j - 1] % k) % k;
			ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1] + (!c[i][j])	;
		}
		ans[i][i + 1] = ans[i][i];
	}
}
signed main() {
	T = read(), k = read();
	pre_work();
	while (T--){
		n = read(), m = read();
		printf("%lld
", m < n ? ans[n][m] : ans[n][n]);		
	}
	return 0;
}

D2T2 蚯蚓

乍一看以为是个优先队列的(sb)题,冷静看了看数据范围发现优先队列过不去

发现题目隐含着很妙的单调性,因为每次取的是最长的蚯蚓切开,切的方式(切割的系数)是一样的,那么一定有先切开的蚯蚓比后切开的蚯蚓长这个结论

所以开三个单调队列,一个维护原来的蚯蚓,一个维护(lfloor px floor),一个维护(x - lfloor px floor),每次取出三个队列的队头比较一下再切割即可

再维护一下时间增长,查询时直接把增量加上去,每次切割出的两条新蚯蚓不会得到这一秒的增长量,那么在它们的值上减一下即可

#include<bits/stdc++.h>
#define int long long
#define N (8000000 + 50)
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
	return cnt * f;
}
int n, m, q, u, v, t, ans, x, siz, a[N];
double p;
int q1[N], q2[N], q3[N];
int h1 = 1, h2 = 1, h3 = 1, t1, t2, t3;
priority_queue<int> Q;
inline int Max(int a, int b, int c) {return max(max(a, b), c);}
inline int get_Max() {
	if (Max(q1[h1], q2[h2], q3[h3]) == q1[h1]) {
		if (h1 <= t1) return q1[h1];
		else if (q2[h2] > q3[h3] && h2 <= t2) return q2[h2];
		else return q3[h3];
	}
	if (Max(q1[h1], q2[h2], q3[h3]) == q2[h2]) {
		if (h2 <= t2) return q2[h2];
		else if (q1[h1] > q3[h3] && h1 <= t1) return q1[h1];
		else return q3[h3];
	}
	if (Max(q1[h1], q2[h2], q3[h3]) == q3[h3]) {
		if (h3 <= t3) return q3[h3];
		else if (q2[h2] > q1[h1] && h2 <= t2) return q2[h2];
		else return q1[h1];
	}
}
inline void POP() {
	if (get_Max() == q1[h1]) {
		if (h1 <= t1) ++h1;
		else if (q2[h2] > q3[h3] && h2 <= t2) ++h2;
		else ++h3;
		return;
	}
	if (get_Max() == q2[h2]) {
		if (h2 <= t2) ++h2;
		else if (q1[h1] > q3[h3] && h1 <= t1) ++h1;
		else ++h3;
		return;
	}
	if (get_Max() == q3[h3]) {
		if (h3 <= t3) ++h3;
		else if (q2[h2] > q1[h1] && h2 <= t2) ++h2;
		else ++h1;
		return;
	}
}
signed main() {
//	freopen("1.in", "r", stdin);
	n = read(), m = read();
	if (m > 1000000) {
		q = read(), u = read(), v = read(), t = read();
		p = (double)u / v;
		for (register int i = 1; i <= n; ++i) a[i] = read();
		sort (a + 1, a + n + 1);
		for (register int i = n; i >= 1; --i) q1[++t1] = a[i];
		for (register int i = 1; i <= m; ++i) {
			if (i % t == 0) {
				ans = get_Max();
				printf("%lld ", ans + (i - 1) * q);
			}
			x = get_Max() + (i - 1) * q;
			POP();
			q2[++t2] = x - (int)(p * (double) x) - i * q, q3[++t3] = (int)(p * (double) x) - i * q;
		}
		printf("
");
		for (register int i = 1; i <= n + m; ++i) {
			if (i % t == 0) printf("%lld ", get_Max() + m * q);
			POP();
		}
	} else {
		q = read(), u = read(), v = read(), t = read();
		p = (double)u / (double) v;
		for (register int i = 1; i <= n; ++i) x = read(), Q.push(x);
		for (register int i = 1; i <= m; ++i) {
			if (i % t == 0) { 
				ans = Q.top();
				printf("%d ", ans + (i - 1) * q);
			}
			x = Q.top() + (i - 1) * q; Q.pop();
			Q.push((int)(p * (double)x) - i * q), Q.push(x - (int)(p * (double)x) - i * q);
		}
		printf("
");
		siz = Q.size();
		for (register int i = 1; i <= siz; ++i) {
			if (i % t == 0) printf("%d ", Q.top() + m * q);
			Q.pop();
		}
	}
	return 0;
}

D2T3 愤怒的小鸟

神仙状压,很多题解的做法复杂度是假的

记录一下分析过程

因为这一年别的题没有dp所以我们考虑dp

看看数据范围,(n leq 18),考虑怎么状压一下

参照洛谷第一篇题解的思路,想想怎么样设计状态

(dp[S])表示已经死了的猪的集合为(S),要达到这个状态(S)所需要发射的鸟有多少只

首先两两预处理一下经过这两点的抛物线解析式(反正只可能是形如(ax^2 + bx)的形式,怎么瞎搞都可),注意跳过(x)相等的情况,然后把这两点塞进(line[a][b])这个集合内,(line[a][b])表示抛物线(ax^2 + bx)经过的所有点的集合状态

考虑一次打一条抛物线的情况,一次打一只猪的情况以及初始状态,得到最朴素的状态转移方程

[dp[0][0] = 0\ dp[S|line[i][j]] = min(dp[S] + 1)\ dp[S|(1<<(i - 1))] = min(dp[S] + 1)\ ]

冷静分析一波发现这个的复杂度是(O(Tn^2 2^n))的,然鹅并跑不过

考虑优化

对于每个集合(S),我们发现第一只打不着的猪(即满足(S&(1<<(x - 1)))的最小正整数(x)),即使我们现在转移的时候不打它,之后也得回过头来把它打掉,于是预处理一下就能做到(O(Tn2^n)),不过这里没有预处理,但也差不多

#include<bits/stdc++.h>
#define INF (1000000000 + 7)
#define N (20 + 5)
#define eps 1e-10
using namespace std;
inline int read() {
	int cnt = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
	while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
	return cnt * f;
}
int t, n, m, f[2000000], num[N][N];
double x[N], y[N];
bool same(double x, double y) {return fabs(x - y) < eps;}
int main() {
	t = read();
	while (t--) {
		n = read(), m = read();
		for (register int i = 1; i <= n; ++i) scanf("%lf%lf", &x[i], &y[i]);
		for (register int i = 1; i <= n; ++i) 
			for (register int j = 1; j <= n; ++j) num[i][j] = 0;
		for (register int i = 1; i < n; ++i)
			for (register int j = i + 1; j <= n; ++j) {
				if (same(x[i], x[j])) continue;
				double a = (y[j] / x[j] - y[i] / x[i]) / (x[j] - x[i]);
				if (a >= 0) continue;
				double b = y[i] / x[i] - a * x[i];
				for (register int k = 1; k <= n; ++k) 
					if (same(a * x[k] + b, y[k] / x[k])) num[i][j] |= (1 << (k - 1));
			}
		for (register int i = 0; i <= (1 << n) - 1; ++i) f[i] = INF; f[0] = 0;
		for (register int i = 0; i <= (1 << n) - 1; ++i) 
			for (register int j = 1; j <= n; ++j) if (!(i & (1 << j - 1))) {
				for (register int k = j; k <= n; ++k) {
					if (j == k) f[i|(1 << (j - 1))] = min(f[i | (1 << (j - 1))], f[i] + 1);
					if (same(x[j], x[k])) continue;
					f[i | num[j][k]] = min(f[i | num[j][k]], f[i] + 1);
				}
				break;
			}
		printf("%d
", f[(1 << n) - 1]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/kma093/p/11808993.html