2020.02.01【NOIP提高组】模拟A 组 总结

啊啊啊,开局不利啊。。。

估分:\(50 + 30 + 0 = 80\)
考场:\(20 + 0 + 0 = 20\)

\(T1\)

一开始想了个贪心,手玩了几个数据后就证萎了。
然后开始思考\(DP\),首先\(O(n^2)DP\)很容易想到。
\(f[i][j]\)表示一个以\(i\)结尾,另一个以\(j\)结尾的最小值。另\(i>j\)
发现当\(j!=i-1\)时,一定从\(f[i-1][j]\)转移,否则从\(f[j][k]\)转移。\(n^2\)枚举。
第一维可以开滚动。然后就\(50\)分了。(作死又改成伪贪心,只有\(20\)了)
发现转移是:前面一坨都加一个固定的值,\(i-1\)位则是在前面的\(min\)中加一个值。
然后想到线段树。但发现那个值是不固定的,然后就弃疗了。

\(solution\)

我们发现对于每个答案\(f[n][i]\)都可以看成\(f[i][i-1]+sum[n]-sum[i]\)
所以其实我们只需要求出每个\(f[i][i-1]\)即可(设\(g[i]\)表示\(f[i][i-1]\)
方程式:\(g[i]=min(g[j]+sum[i-1]-sum[j]+|h[i]-h[j-1]|)\)
由于有\(abs\),我们可以开两个线段树。一个表示\(h[i]>=h[j-1]\)时的值,另一个相反。
然后我们只需要维护线段树即可。操作简单方便。

\(T2\)

一开始没有什么想法。
后来\(3min\)打了个\(30\)分,结果细节打错了。。。
正解回过头来想想看,发现其实也是有一定的可想性的。
我们发现\(n\)很小,但是边数很大,所以一定有很多的重边。
所以我们可以从重边依照\(k1,k2\)来比较大小从而发现一定的规律。(式子自己推一推)
然后我们可以将\(u\)排序,然后发现只有凸包上的顶点才是有用的。
所以我们可以将没有用的删掉。然后在按照\(k1/k2\)来选点,我们发现最近\(k1/k2\)的且大于它的更优(当\(u1>u2\))时。
所以我们只需要将询问按照\(k1/k2\)排一边序,然后对于两两点之间选的边就一定是不断往后的了。我们可以用\(prim\)来求答案。
代码的常数巨大,请小心驾驶。

\(code\)

#include <cstdio>
#include <vector>
#include <algorithm>
#define N 200010
#define db double
#define fo(x, a, b) for (register int x = (a); x <= (b); x++)
#define fd(x, a, b) for (register int x = (a); x >= (b); x--)
using namespace std;
struct xw{db k1, k2; int fr;}ask[N];
int n, m, Q, point[37][37], num[37][37];
pair<int, int> b[N];
vector<pair<int, int> > edge[37][37];
db ans = 0, aw[N], eg[37][37];

inline int read()
{
	int x = 0, f = 0; char c = getchar();
	while (c < '0' || c > '9') f = (c == '-') ? 1 : f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return f ? -x : x;
}

inline bool cmp(pair<int, int> x, pair<int, int> y) {return x.first == y.first ? x.second < y.second : x.second > y.second;;}

inline bool cmp1(xw x, xw y) {return x.k1 * y.k2 < y.k1 * x.k2;}

inline bool cmp_first(pair<int, int> x, pair<int, int> y) {return x.first == y.first ? x.second > y.second : x.first > y.first;}

inline db calc(pair<int, int> x, pair<int, int> y) {return (db)(y.second - x.second) / (x.first - y.first);}

db prim()
{
	db ans = 0.0;
	static db d[37];
	static bool tag[37];
	tag[1] = 1, d[1] = 0;
	fo(i, 2, n) d[i] = eg[1][i], tag[i] = 0;
	fo(i, 2, n)
	{
		int k = 0;
		fo(j, 1, n)
			if (! tag[j] && (! k || d[j] < d[k])) k = j;
		if (! k) continue;
		tag[k] = 1, ans += d[k];
		fo(j, 1, n)
			if (! tag[j]) d[j] = min(d[j], eg[j][k]);
	}
	return ans;
}

int main()
{
	n = read(), m = read(), Q = read();
	fo(i, 1, m)
	{
		int x = read(), y = read(), u = read(), v = read();
		if (x < y) edge[x][y].push_back(make_pair(u, v));
		else edge[y][x].push_back(make_pair(u, v));
	}
	fo(i, 1, n)
		fo(j, i + 1, n)
		{
			int len = edge[i][j].size(), len1 = -1;
			fo(k, 0, len - 1) b[k + 1] = edge[i][j][k];
			std::sort(b + 1, b + len + 1, cmp_first);
			edge[i][j].clear();
			fo(k, 1, len)
			{
				while (len1 >= 0 && (b[k].second <= edge[i][j][len1].second || (len1 >= 1 && calc(edge[i][j][len1], edge[i][j][len1 - 1]) > calc(b[k], edge[i][j][len1]))))
					edge[i][j].pop_back(), len1--;
				edge[i][j].push_back(b[k]), len1++;
			}
			num[i][j] = len1;
		}
	fo(i, 1, Q) scanf("%lf%lf", &ask[i].k1, &ask[i].k2), ask[i].fr = i;
	std::sort(ask + 1, ask + Q + 1, cmp1);
	fo(k, 1, Q)
	{
		fo(i, 1, n)
			fo(j, i + 1, n)
			{
				while (point[i][j] < num[i][j] && ask[k].k1 > calc(edge[i][j][point[i][j]], edge[i][j][point[i][j] + 1]) * ask[k].k2) point[i][j]++;
				if (point[i][j] <= num[i][j])
					eg[i][j] = eg[j][i] = edge[i][j][point[i][j]].first * ask[k].k1 + edge[i][j][point[i][j]].second * ask[k].k2;
				else eg[i][j] = eg[j][i] = 1e16;
			}
		aw[ask[k].fr] = prim();
	}
	fo(i, 1, Q) printf("%.3lf\n", aw[i]);
	return 0;
}

\(T3\)

完全没有什么想法。
题解也没有清楚理解。

总结

要给自己留足时间去打码。
贪心一定要先证一证,万一证萎了呢?
想题是一步步来的,不要一上来就刚正解。

posted @ 2020-02-01 17:50  jz929  阅读(88)  评论(0编辑  收藏  举报