啊啊啊,开局不利啊。。。
估分:(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
", aw[i]);
return 0;
}
(T3)
完全没有什么想法。
题解也没有清楚理解。
总结
要给自己留足时间去打码。
贪心一定要先证一证,万一证萎了呢?
想题是一步步来的,不要一上来就刚正解。