C题题意:有n台飞机,起飞时间依次从1到n,但是现在会延期k分钟,也就是现在允许飞行的时间为k+1到k+n,每台飞机延期1分钟会有一个代价ci,并且不允许提前飞行,问如何安排飞行顺序使得代价最小。
思路:对于每台飞机来说,延期代价有大有小,那么延期对于代价大的来说,会影响更大,所以应该让代价大的飞机先选飞行时间。
用数学方法也同样可以得到该结论。读者有兴趣的话可以私聊博主,在这就不赘述了,毕竟比赛只有2个小时,不需要那么严谨。
按照上面的思路,先按照代价排序,然后让每个飞机都从自己原先起飞的时间往后遍历,看哪个时间没有被选,然后选它。。这样近似暴力的遍历会果断的TLE(哭),那么我们可以换个角度看看,我们也可以看成时刻选择飞机(精髓)。每个时刻可以选择原先在这个时刻之前起飞的代价最大的那个飞机。
代码如下:
#include<iostream>
#include<queue>
using namespace std;
typedef pair<long long, long long>P;
priority_queue<P>Q;
int theans[300005];
int main()
{
int n, k;
cin >> n >> k;
int temp;
for (int i = 1;i <=k;i++)
{
scanf("%d", &temp);
Q.push(P(temp, i));
}
long long ans = 0;
for (int i = k + 1;i <= n + k;i++)
{
if (i <= n)
{
scanf("%d", &temp);
Q.push(P(temp, i));
}
P a = Q.top();Q.pop();
ans +=1LL* (i - a.second)*a.first;
theans[a.second] = i;
}
cout << ans << endl;
for (int i = 1;i <= n;i++)
{
if (i != 1)
printf(" ");
printf("%d", theans[i]);
}
return 0;
}
D题题意:又是飞机。。有很多大使,原先在不同的位置(非0地点),都要去0地点,给出飞机起飞的时间,起点,终点(起点或终点中必有一个0)和代价,我们要在保证所有大使能够共同在0地点呆大于等于k天,问如何选飞机飞行可以满足题意并且花费最低。
思路:最直接的思路,求每个点的前缀最小值和后缀最小值,但求后缀最小值得往后推k天,也就是代表在第i天前和第i+k天后的最小花费。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxm = 200005;
struct node
{
int d, f, t;
long long c;
bool operator<(const node &b)const
{
return d < b.d;
}
}nodes[maxm];
const long long INF = 1e12;
long long L[1000005], R[1000005];
long long mincost[maxm];
int main()
{
long long n;
int m, k;
cin >> n >> m >> k;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d%lld", &nodes[i].d, &nodes[i].f, &nodes[i].t, &nodes[i].c);
}
sort(nodes + 1, nodes + 1 + m);
for (int i = 0;i <= 1000001;i++)
L[i] = R[i] = n*INF;
for (int i = 1;i <= n;i++)mincost[i] = INF;
for (int i = 1, j = 1;i <= 1000000 - k + 1;i++)
{
L[i] = L[i - 1];
while (nodes[j].d<i&&j <= m)
{
if (!nodes[j].t&&nodes[j].c < mincost[nodes[j].f])
{
L[i] = L[i] - mincost[nodes[j].f] + nodes[j].c;
mincost[nodes[j].f] = nodes[j].c;
}
j++;
}
}
for (int i = 1;i <= n;i++)mincost[i] = INF;
for (int i = 1000000 - k + 1, j = m;i;i--)
{
R[i] = R[i + 1];
while (nodes[j].d >= i + k&&j)
{
if (!nodes[j].f&&nodes[j].c < mincost[nodes[j].t])
{
R[i] = R[i] - mincost[nodes[j].t] + nodes[j].c;
mincost[nodes[j].t] = nodes[j].c;
}
j--;
}
}
long long ans = 1e18;
for (int i = 1;i <= 1000000;i++)
ans = min(ans, L[i] + R[i]);
printf("%lld", ans > INF ? -1 : ans);
return 0;
}
巧妙的写法,就算求出了一个最小值,如何判断所有人都到达了?试想飞机飞一次的代价为1e6,而最多有1e5个人,所有都取最大,那么来回最大花费也是2e11,所以我们将总代价初始成n*1e12,如果都可以飞到,那么ans就会是一个小于1e12的数,如果不能飞到,那么肯定是大于1e12的。
E题题意:好难讲啊这个题意。。给你一个n*n的矩形,每行每列都会有一个点,所以有n个点。一个区域被称为beautiful,如果它的任意一个对角都被点覆盖。所以一个矩形有n*(n-1)/2个beautiful区域。
现在给你一个区域,问你至少包含这个区域一块的beautiful区域有多少个。
思路:如果理解了题意,在思考下,就会容易的想到求所有不包括该区域的beautiful区域个数,然后总个数减去这个就行了。
那么根据容斥原理,我们先减掉在给定区域下部,上部,左部,右部的所有beautiful区域个数,然后加上左下角,右下角,左上角,右上角的beautiful区域个数。
现在问题就转化成了如何快速求出某区域被标记点的个数。
博主这里提供两种解法,读者可以自行体会其中的利弊。
第一种,这种题一看就和线段树有关,那么如何写呢?定义到第j列的线段树的区间[a,b]维护从第1列到第j列的第a行到第b行的标记点个数。
那么在计算第j列时,第j列的所有区间和应在j-1的前提下更新,如果该区间和j-1的区间相同,那么仍然用该该点做儿子,如果不同则新开一个点。专业名词可持久化线段树。
代码如下:
#include<iostream>
using namespace std;
const int maxn = 200005;
int Left[maxn*25];
int Right[maxn * 25];
int tot;
int rt[maxn];
int Sum[maxn * 25];
void insert(int L, int l, int r, int bef, int &idx)
{
idx = ++tot;
Left[idx] = Left[bef];
Right[idx] = Right[bef];
Sum[idx] = Sum[bef] + 1;
if (l == r)return;
int mid = l + r >> 1;
if (L <= mid)
insert(L, l, mid, Left[bef], Left[idx]);
else insert(L, mid + 1, r, Right[bef], Right[idx]);
}
typedef long long ll;
ll work(ll a)
{
return a*(a - 1) / 2;
}
int query(ll L, ll R, ll l, ll r, ll idx)
{
if (L <= l&&r <= R)return Sum[idx];
if (L > R)return 0;
ll mid = l + r >> 1;
if (R <= mid)return query(L, R, l, mid, Left[idx]);
else if (L >= mid + 1)return query(L, R, mid + 1, r, Right[idx]);
else return query(L, mid, l, mid, Left[idx]) + query(mid + 1, R, mid + 1, r, Right[idx]);
}
int main()
{
int n, q;
cin >> n >> q;
int temp;
for (int i = 1;i <= n;i++)
{
scanf("%d", &temp);
insert(temp, 1, n, rt[i - 1], rt[i]);
}
ll l, d, r, u;
while (q--)
{
scanf("%lld%lld%lld%lld", &l, &d, &r, &u);
ll ans = work(n);
ans -= work(l - 1);
ans -= work(n - r);
ans -= work(d-1);
ans -= work(n - u);
temp = query(1, d - 1, 1, n, rt[l - 1]);
ans += work(1LL*temp);
temp = query(u + 1, n, 1, n, rt[l - 1]);
ans += work(1LL*temp);
temp = query(1, d - 1, 1, n, rt[n]) - query(1, d - 1, 1, n, rt[r]);
ans += work(1LL*temp);
temp = query(u + 1, n, 1, n, rt[n]) - query(u + 1, n, 1, n, rt[r]);
ans += work(1LL*temp);
cout << ans << endl;
}
return 0;
}
这份代码差点见不到各位了,跑了1600+ms,我们来分析一下复杂度。
建树每一列就要建一次,o(nlogn),每次查询询问4次和,o(4*q*logn)(不知道有没有分析对。。),总之,博主感觉跑得慢的主要原因就是建树的时间长了。
既然建树时间长,那么就不建这么多树了吧。
第二种做法,首先将在线操作变成离线操作,然后计算第i列左上角和左下角的时候就从1到i-1依次在树上加点,然后查询。计算右上角和右下角就从后往前加点。
这样就变成了简单的维护区间sum的线段树啦。
博主就简化用树状数组操作了。
代码如下:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define lowbit(x) ((x)&(-x))
typedef long long ll;
const int maxn = 200005;
int sum[maxn];
int p[maxn];
ll work(int a)
{
return 1LL*a*(a - 1) / 2;
}
struct node
{
int l, r, u, d,idx;
ll lu, ld, ru, rd;
}nodes[maxn];
void add(int x,int num)
{
while (x<maxn)
{
sum[x] += num;
x += lowbit(x);
}
}
int find(int x)
{
int ans = 0;
while (x)
{
ans += sum[x];
x -= lowbit(x);
}
return ans;
}
bool cmp1(node a, node b)
{
return a.l < b.l;
}
bool cmp2(node a, node b)
{
return a.r < b.r;
}
ll ans[maxn];
int main()
{
int n, q;
cin >> n >> q;
for (int i = 1;i <= n;i++)
scanf("%d", &p[i]);
for (int i = 1;i <= q;i++)
scanf("%d%d%d%d", &nodes[i].l, &nodes[i].d, &nodes[i].r, &nodes[i].u), nodes[i].idx = i;
sort(nodes + 1, nodes + 1 + q, cmp1);
int now = 1;
while (nodes[now].l==1)
{
now++;
}
for (int i = 1;i < n;i++)
{
add(p[i],1);
while (nodes[now].l-1==i&&now<=q)
{
nodes[now].lu = find(n) - find(nodes[now].u);
nodes[now].ld = find(nodes[now].d - 1);
now++;
}
}
memset(sum, 0, sizeof(sum));
sort(nodes + 1, nodes + 1 + q, cmp2);
now = q;
while (nodes[now].r == n)now--;
for (int i = n;i > 1;i--)
{
add(p[i],1);
while (nodes[now].r + 1 == i&&now)
{
nodes[now].ru = find(n) - find(nodes[now].u);
nodes[now].rd = find(nodes[now].d - 1);
now--;
}
}
ll num = work(n);
ll temp;
for (int i = 1;i <= q;i++)
{
temp = num;
temp = temp - work(nodes[i].l - 1) - work(nodes[i].d - 1) - work(n - nodes[i].u) - work(n - nodes[i].r);
temp += work(nodes[i].lu) + work(nodes[i].ld) + work(nodes[i].ru) + work(nodes[i].rd);
ans[nodes[i].idx] = temp;
}
for (int i = 1;i <= q;i++)
printf("%lld\n", ans[i]);
return 0;
}
代码快了许多,483ms跑过,学会了再也不怕TLE啦。