模拟赛20181101 雅礼 Wearry 施工 蔬菜 联盟

% Day2 Solution
% Wearry
% Stay determined!

施工

  
fif_{i} 表示考虑前 ii 个建筑, 并且第 ii 个建筑的高度不变的答案, 每次转移时枚举上一个不变的建筑编号,
中间的一段一定变成相同的高度, 并且高度小于等于两端的高度.

  
假设从 fjf_j 转移且中间高度为 tt, 则:

fi=k=j+1i1(thk)2+c(h[j]+h[i]2t)f_i = sum_{k=j+1}^{i-1} (t-h_k)^2 + c(h[j] + h[i] - 2t)

  
这样中间的高度可以 O(1)O(1) 求二次函数的对称轴确定.
考虑优化转移, 因为中间高度要小于两端, 所以最多只有一个 hj>hih_j > h_ijj 能够转移.
可以维护关于高度的单调栈, 这样有效的转移次数就是 O(n)O(n) 的.

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int MAXN = 1000005;
int n, h[MAXN], C;
int stk[MAXN], indx;
LL f[MAXN], sum[2][MAXN];

LL solve(int j, int k, int i)
{
	LL a = i - j - 1;
	LL b = -2ll * (sum[0][i-1] - sum[0][j]) - (i != n+1) * C - (j != 0) * C;
	LL c = sum[1][i-1] - sum[1][j] + 1ll * (i != n+1) * h[i] * C + 1ll * (j != 0) * h[j] * C;
	LL t = round(-1. * b / 2 / a);
	t = max(t, 1ll*h[k]);
	t = min(t, 1ll*h[i]);
	t = min(t, 1ll*h[j]);
	return a * t * t + b * t + c;
}

int main ()
{
	freopen("construct.in", "r", stdin);
	freopen("construct.out", "w", stdout);
	scanf("%d%d", &n, &C);
	for(int i = 1; i <= n; i++)
		scanf("%d", &h[i]),
		sum[0][i] = sum[0][i-1] + h[i],
		sum[1][i] = sum[1][i-1] + 1ll*h[i]*h[i];
	h[0] = MAXN; h[n+1] = MAXN-1;
	stk[++indx] = 0;
	for(int i = 1; i <= n+1; i++)
	{
		f[i] = f[i-1] + (i == 1 || i == n+1 ? 0 : 1ll * C * abs(h[i] - h[i-1]));
		while(indx && h[stk[indx]] <= h[i])
			f[i] = min(f[i], f[stk[indx-1]] + solve(stk[indx-1], stk[indx], i)),indx--;
		stk[++indx] = i;
	}
	printf("%lld
", f[n+1]);
}

蔬菜

  
当蔬菜的出现次数较多时可以对每种蔬菜维护二维前缀和并根据定义计算答案;当蔬菜的出现次数较少时考虑平方的转化, 相当于计算有多少个点对被询问区域包含, 实际上等价于四维偏序.

  
综合分析两种算法的复杂的并选取合适的出现次数的分界值 kk, 复杂度为 O(n2k(n2+q)+(n2k+q)log3n)O(frac{n^2}{k} (n^2 + q) + (n^2k + q) log^3 n).
k=n2+qlog3nk = sqrt{frac{n^2+q}{log^3 n}} 最优.
法二:二维卡常莫队(似乎比正解快)

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
using namespace std;
namespace Fread
{
    char cb[1<<15], *cs, *ct;
    #define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
    template<class T>inline void read(T &num)
    {
        char ch; int flag=1;
        while(!isdigit(ch=getc()))if(ch=='-')flag=-flag;
        for(num=ch-'0';isdigit(ch=getc());num=num*10+ch-'0');
        num*=flag;
    }
}
using namespace Fread;
const int MAXN = 205;
const int MAXM = 100005;
const int BLOCK = 8;
int n, m, q, mz[MAXN][MAXN], tot, ans[MAXM];
int A, B, X, Y, cnt[MAXN*MAXN], Ans;

inline int blk(int x) { return x/BLOCK; }
struct node { int a, b, x, y, id; }query[MAXM];
struct node2 { int v, x, y; }p[MAXN*MAXN];
inline bool cmp(const node &i, const node &j) { return blk(i.a) == blk(j.a) ? blk(i.b) == blk(j.b) ? blk(i.x) == blk(j.x) ? i.y < j.y : blk(i.x) < blk(j.x) : blk(i.b) < blk(j.b) : blk(i.a) < blk(j.a); }
inline bool cmp2(const node2 &i, const node2 &j) { return i.v < j.v; }

inline void Move(node now)
{
    while(A > now.a) { A--; for(register int j = B; j <= Y; j++) Ans += ((cnt[mz[A][j]])++<<1) + 1; }
    while(B > now.b) { B--; for(register int i = A; i <= X; i++) Ans += ((cnt[mz[i][B]])++<<1) + 1; }
    while(X < now.x) { X++; for(register int j = B; j <= Y; j++) Ans += ((cnt[mz[X][j]])++<<1) + 1; }
    while(Y < now.y) { Y++; for(register int i = A; i <= X; i++) Ans += ((cnt[mz[i][Y]])++<<1) + 1; }
    while(A < now.a) { for(register int j = B; j <= Y; j++) Ans += -((cnt[mz[A][j]])--<<1) + 1; A++; }
    while(B < now.b) { for(register int i = A; i <= X; i++) Ans += -((cnt[mz[i][B]])--<<1) + 1; B++; }
    while(X > now.x) { for(register int j = B; j <= Y; j++) Ans += -((cnt[mz[X][j]])--<<1) + 1; X--; }
    while(Y > now.y) { for(register int i = A; i <= X; i++) Ans += -((cnt[mz[i][Y]])--<<1) + 1; Y--; }
}

int main()
{
    read(n), read(m), read(q);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            read(p[tot].v), p[tot].x = i, p[tot++].y = j;
    sort(p, p + tot, cmp2); int cntt = 0;
    for(int i = 0; i < tot; i++)
        if(p[i].v != p[i+1].v) mz[p[i].x][p[i].y] = cntt++;
        else mz[p[i].x][p[i].y] = cntt;
    cnt[mz[0][0]]++; Ans++;
    for(int i = 0; i < q; i++)
        read(query[i].a), read(query[i].b), read(query[i].x), read(query[i].y), query[i].id = i,
        query[i].a--, query[i].b--, query[i].x--, query[i].y--;
    sort(query, query + q, cmp);
    for(int i = 0; i < q; i++)
        Move(query[i]), ans[query[i].id] = Ans;
    for(int i = 0; i < q; i++) printf("%d
", ans[i]);
}

联盟

  
危险程度就是树的直径, 断开边后会得到两个联通块, 假设两个联通块的直径分别为 l1,&ThinSpace;l2l_1,\, l_2,
根据直径的性质, 连接两个联通块后新的直径长度最小是
max{l1,l2,l12+l22+1}max{ l_1, l_2, left lceil frac{l_1}{2} ight ceil + left lceil frac{l_2}{2} ight ceil + 1 }.

  
考虑维护, 由于合并两个联通块后新的直径的两端点一定会在原来的直径端点的并中产生,
可以处理每一个子树的直径端点, 每次考虑 iifaifa_i 的边时只需分别求出两个块的直径端点即可,
在每个点上维护子树前缀联通块和后缀联通块的直径端点即可快速合并.

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 300005;
inline void read(int &num)
{
	char ch; int flag = 1;
	while(!isdigit(ch=getchar()))if(ch=='-')flag=-flag;
	for(num=ch-'0';isdigit(ch=getchar());num=num*10+ch-'0');
	num *= flag;
}
int n, fir[MAXN], to[MAXN*2], nxt[MAXN*2], id[MAXN*2], cnt, FAEDGE[MAXN];
inline void Add(int u, int v, int i)
{
	to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; id[cnt] = i;
	to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt; id[cnt] = i;
}

int dep[MAXN], fa[MAXN];

inline int dfs1(int u, int ff)
{
	dep[u] = dep[ff] + 1;
	int mx = u, x;
	for(int i = fir[u]; i; i = nxt[i])
		if(to[i] != ff)
		{
			x = dfs1(to[i], u);
			if(dep[x] > dep[mx])
				mx = x;
		}
	return mx;
}

inline int dfs2(int u, int ff)
{
	dep[u] = dep[fa[u]=ff] + 1;
	int mx = u, x;
	for(int i = fir[u]; i; i = nxt[i])
		if(to[i] != fa[u])
		{
			FAEDGE[to[i]] = id[i];
			x = dfs2(to[i], u);
			if(dep[x] > dep[mx])
				mx = x;
		}
	return mx;
}

bool flag[MAXN];
int far[MAXN];

inline void dfs3(int u)
{
	for(int i = fir[u]; i; i = nxt[i])
		if(to[i] != fa[u] && !flag[to[i]])
			dfs3(to[i]), far[u] = max(far[u], far[to[i]]+1);
}

int Ans = 0x7f7f7f7f, k, st[MAXN];

int son[MAXN], f[MAXN], g[MAXN];

bool cmp(int a, int b)
{
	return FAEDGE[a] < FAEDGE[b];
}

int pre[MAXN];
inline int dfs4(int u, int ff, int ooo)
{
	dep[u] = dep[pre[u]=ff] + 1;
	int mx = u;
	for(int i = fir[u]; i; i = nxt[i])
		if(to[i] != ff && to[i] != ooo)
		{
			int tmp = dfs4(to[i], u, ooo);
			if(dep[tmp] > dep[mx]) mx = tmp;
		}
	return mx;
}
int main ()
{
	freopen("league.in", "r", stdin);
	freopen("league.out", "w", stdout);
	read(n);
	int x, y;
	for(int i = 1; i < n; i++)
		read(x), read(y), Add(x, y, i);
	int rt = dfs1(1, 0);
	int lv = dfs2(rt, 0);
	for(int i = lv; i; i = fa[i]) flag[i] = 1, son[fa[i]] = i;
	for(int i = lv; i; i = fa[i]) dfs3(i);
	for(int i = rt; i; i = son[i])
		f[i] = max(far[i]+dep[i]-dep[rt], f[fa[i]]);
	for(int i = lv; i; i = fa[i])
		g[i] = max(far[i]+dep[lv]-dep[i], g[son[i]]);
	for(int i = lv; i != rt; i = fa[i])
	{
		if(max((g[i]+1)/2 + (f[fa[i]]+1)/2 + 1, max(g[i], f[fa[i]])) < Ans)
			Ans = max((g[i]+1)/2 + (f[fa[i]]+1)/2 + 1, max(g[i], f[fa[i]])), st[k=1] = i;
		else if(max((g[i]+1)/2 + (f[fa[i]]+1)/2 + 1, max(g[i], f[fa[i]])) == Ans)
			st[++k] = i;
	}
	printf("%d
", Ans);
	if(Ans == dep[lv]-dep[rt])
	{
		printf("%d", n-1);
		for(int i = 1; i < n; i++)
			printf(" %d", i);
		putchar('
');
		for(int i = fir[1]; i; i = nxt[i])
		{
			printf("%d %d %d %d
", i, to[i], i, to[i]); return 0;
		}
	}
	printf("%d", k);
	sort(st + 1, st + k + 1, cmp);
	for(int i = 1; i <= k; i++)
		printf(" %d", FAEDGE[st[i]]);
	putchar('
');
	printf("%d %d", st[1], fa[st[1]]);
	memset(dep, 0, sizeof dep);
	int D1 = dfs4(lv, 0, fa[st[1]]); int len = (dep[D1]) >> 1;//注意此处求直径中点时要注意dep的值域,是从0开始还是1开始 用不同的取整方式求len
	while(len--) D1 = pre[D1];
	memset(dep, 0, sizeof dep);
	int D2 = dfs4(rt, 0, st[1]); len = (dep[D2]) >> 1;
	while(len--) D2 = pre[D2];
	printf(" %d %d
", D1, D2);
}

PS:考试时SB地认为直径的中点一定是重心,然而一个扫把图就说明问题了。只有55分

原文地址:https://www.cnblogs.com/Orz-IE/p/12039492.html