DTOJ 2022.11.08 测试 题解

A 光

portal

题目大意

有四个格子,左上、右上、左下、右下分别可以填一个值 \(a,b,c,d\),每个格子上的值 \(x\) 可以对自己贡献 \(x\) 的亮度,对相邻的两个格子贡献 $\lfloor \frac{x}{2} \rfloor $ 的亮度 ,对对角线的格子贡献 $\lfloor \frac{x}{4} \rfloor $ 的亮度 ,最后要满足每个格子的亮度分别大于等于 \(A,B,C,D\)\(A,B,C,D\le 1500\))求 \(a+b+c+d\) 的最小值

题解

1. \(O(n^4)\)

直接枚举四个.(时间复杂度中的 \(n\) 表示 \(max\{A,B,C,D\}\)

2. \(O(n^3)\)

只需要枚举三个,算出每个格子还需要多少,剩下的一个就可以 \(O(1)\) 算了.

3. \(O(n^2\log n)\)

二分答案,然后枚举对角线上的点,这时候可以算出剩下的两个格子还需要多少,算出大致的值可以上下枚举一下.

4. \(O(1)\)

(准确来说是 \(O(2^4\cdot 4^3\cdot 5^4)\) ,这么说其实跟上面那个差不多快)

考场上不知道为什么想到了这个神奇做法(感觉还是很可行的)

注意到限制是一系列的线性不等式,可以考虑线性规划()

(普通的线性规划其实就是二元一次不等式组,然后求 \(\max\{ax+by\}\) 这样的东西, 这个时候把不等式组看成一系列直线切出来的半平面的交,然后 \(\max\{ax+by\}\) 可以看成 \(ax+by=t\) 这条直线与可行区域有交点,\(t\) 最大的时候其实就一定是一个多边形的顶点)

改成四元不等式其实差不多,四个方程一定只有一个解,所以一定也只有一个顶点,这个时候一定取到最小值.

但是我们注意到 \(a,b,c,d\geq 0\) 这个条件,所以我们枚举哪些是零,总共有 \(2^4\) 种情况.

最后求出来之后也跟刚才一样上下枚举一下.

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1505;
const int D = 10;
double a[6][6],b[6],B[6];
int res[6];
int ans=N<<2;
void solve()
{
	for(int i=1; i<=4; i++)
	{
		int mnp=1;
		for(int j=1; j<=4; j++) if(a[j][i]>a[mnp][i]) mnp=j;
		for(int j=1; j<=4; j++)
			if(j!=mnp)
			{
				double d=a[j][i]/a[mnp][i];
				for(int k=1; k<=4; k++) a[j][k]-=a[mnp][k]*d;
				b[j]-=b[mnp]*d;
			}
	}
	for(int i=1; i<=4; i++) res[i]=(int)b[i]/a[i][i];
	for(int i=max(res[1]-D,0); i<=res[1]+D; i++) for(int j=max(res[2]-D,0); j<=res[2]+D; j++)
		for(int k=max(res[3]-D,0); k<=res[3]+D; k++) for(int l=max(res[4]-D,0); l<=res[4]+D; l++)
			if(i+j/2+k/2+l/4>=B[1] and j+i/2+l/2+k/4>=B[2] and l+j/2+k/2+i/4>=B[4] and k+i/2+l/2+j/4>=B[3])
				if(i+j+k+l<ans) ans=i+j+k+l;
}
int main()
{
	for(int i=1; i<=4; i++) scanf("%lf",&B[i]);
	for(int t=0; t<16; t++)
	{
		for(int i=1; i<=4; i++) b[i]=B[i];
		for(int i=1; i<=4; i++) for(int j=1; j<=4; j++) a[i][j]=0.5;
		for(int i=1; i<=4; i++) a[i][i]=1,a[i][5-i]=0.25;
		for(int j=1; j<=4; j++) if((t>>(j-1))&1) 
		{
			for(int i=1; i<=4; i++) a[j][i]=0;
			b[j]=0; a[j][j]=1;
		}
		solve();
	}
	printf("%d\n",ans);
	return 0;
}

B 奶茶代金券

portal

还没补题,看着像简单贪心,但细节实在太多了(((((((ww不会写贪心)

C 函数复合

portal

题面

给定 \(n\) 个函数 \(F_1(x), F_2(x), \dots, F_n(x)\),每个函数为以下三种之一:

  1. \(F(x) = x + w\)
  2. \(F(x) = \min(x, w)\)
  3. \(F(x) = \max(x, w)\)

给定 \(q\) 次操作,每次操作为以下四种之一:

  1. \(1 ~ p ~ w\):表示令 \(F_p(x) = x + w\)
  2. \(2 ~ p ~ w\):表示令 \(F_p(x) = \min(x, w)\)
  3. \(3 ~ p ~ w\):表示令 \(F_p(x) = \max(x, w)\)
  4. \(4 ~ x\):表示询问 \((F_n \circ F_{n - 1} \circ \dots \circ F_1)(x)\) 的值,其中 \(F \circ G\) 表示函数 \(F\)\(G\) 的复合,即 \((F \circ G)(x) = F(G(x))\)

请维护以上操作。

对于所有测试数据,保证 \(1 \le n \le 10 ^ 5\)\(1 \le q \le 3 \times 10 ^ 5\)\(op_i \in \{1, 2, 3\}\)\(1 \le p \le n\)\(1 \le x \le 10 ^ 8\),保证任意时刻第 \(1\) 种函数中的 \(w\)\([1, 200]\) 之内,第 \(2,3\) 种函数中的 \(w\)\([1, 10 ^ 8]\) 之内。

题解

这题我测试的时候写得很顺利,对拍都过了,不知道为什么交上去只有 75 分,把 assert 删掉了之后就过了(我直接问号?

呃呃所以怎么写呢

方法1

注意到无论多少次 \(\min,\max,+\) 函数复合,最终的函数一定是形如这样的:

\[f(x)=\begin{align} \left\{ \begin{array}{ll} y_1,x\leq x_1\\ x+b,x_1<x<x_2\\ y_2,x\geq x_2 \end{array} \right\} \end{align} \]

注意到这样函数的复合满足结合律,所以你可以直接在线段树上维护函数,难点主要是怎么合并两个函数

这是我的写法(我对于一个函数维护了它的最小值,最大值和中间那段线的纵截距 \(b\)

friend Kurumi operator^ (Kurumi f, Kurumi g) // Kurumi 是一个函数结构体 (Kurumi 不是狂三是胡桃)
{
    Kurumi res;
    res.ymx=g.work(f.ymx);
    res.ymn=g.work(f.ymn); //结果的最小值一定是这样的
    if(res.ymx==res.ymn) { res.b=0; return res; }
    int rxmn=f.xmn(); int rxmx=f.xmx();
    int t1=f.inv(g.xmn()),t2=f.inv(g.xmx());
    if(t1!=-1 and t1>rxmn) rxmn=t1;
    if(t2!=-1 and t2<rxmx) rxmx=t2;
    res.b=res.ymx-rxmx;
    return res;
}

最后代码贴上来:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5+5, inf = 1e9;
int n,q;
struct Kurumi
{
	int ymn,ymx,b;
	int xmn() { return ymn-b; }
	int xmx() { return ymx-b; }
	int work(int x)
	{
		if(x<=xmn()) return ymn;
		else if(x>=xmx()) return ymx;
		else return x+b;
	}
	int inv(int y)
	{
		if(y<ymn or y>ymx) return -1;
		else return y-b;
	}
	void print()
	{
		printf("ymn=%d ymx=%d b=%d\n",ymn,ymx,b);
	}
	friend Kurumi operator^ (Kurumi f, Kurumi g)
	{
//		printf("f: "),f.print();
//		printf("g: "),g.print();
		Kurumi res;
		res.ymx=g.work(f.ymx);
		res.ymn=g.work(f.ymn);
		if(res.ymx==res.ymn) { res.b=0; return res; }
		int rxmn=f.xmn(); int rxmx=f.xmx();
		int t1=f.inv(g.xmn()),t2=f.inv(g.xmx());
		if(t1!=-1 and t1>rxmn) rxmn=t1;
		if(t2!=-1 and t2<rxmx) rxmx=t2;
		//assert(rxmx-rxmn==res.ymx-res.ymn);
		res.b=res.ymx-rxmx;
		return res;
	}
} fc[N];
struct Sagiri
{
	int l,r;
	Kurumi dat;
} t[N<<2];
#define ls (p<<1)
#define rs (p<<1)|1
void build(int p, int l, int r)
{
//	printf("%d [%d %d]\n",p,l,r);
	
	t[p].l=l,t[p].r=r;
	if(l==r) { t[p].dat=fc[l]; return ; }
	int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	t[p].dat=t[ls].dat^t[rs].dat;
}
void change(int p, int x, const Kurumi &v)
{
	if(t[p].l==t[p].r) { t[p].dat=v; return ; }
	int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid) change(ls,x,v);
	else if(x>mid) change(rs,x,v);
	t[p].dat=t[ls].dat^t[rs].dat;
}
Kurumi query(int p, int l, int r)
{
	if(t[p].l==l and t[p].r==r) return t[p].dat;
	int mid=(t[p].l+t[p].r)>>1;
	if(r<=mid) return query(ls,l,r);
	else if(l>mid) return query(rs,l,r);
	else return query(ls,l,mid)^query(rs,mid+1,r);
}

int main()
{
	scanf("%d",&n);
	for(int i=1,op,w; i<=n; i++)
	{
		scanf("%d%d",&op,&w);
		if(op==1) fc[i]={0,inf,w};
		else if(op==2) fc[i]={0,w,0};
		else fc[i]={w,inf,0};
	}
	build(1,1,n);
	scanf("%d",&q);
	for(int i=1,op,p,w; i<=q; i++)
	{
		scanf("%d",&op);
		if(op==4) 
		{
			scanf("%d",&w);
			Kurumi res=query(1,1,n);
			printf("%d\n",res.work(w));
		}
		else
		{
			scanf("%d%d",&p,&w);
			if(op==1) change(1,p,{0,inf,w});
			else if(op==2) change(1,p,{0,w,0});
			else change(1,p,{w,inf,0});
		}
	}
	return 0;
}

方法2

注意到无论多少次 \(\min, \max, +\) 最终的函数一定是形如 \(\max\{\min\{x+w_1,w_2\},w_3\}\)

两个这种函数也具有可合并性,线段树上维护 \(w_1,w_2,w_3\) 就好啦!

D 积木拼接

portal

题面大意

\(n\) 个蛋糕,每个蛋糕有 \(w_i,h_i\) 。选 \(m\) 个蛋糕满足 \(\sum\limits_{j=1}^mw_{k_j}-\sum\limits_{j=1}^m|h_{k_j}-h_{k_{j+1}}|\) 最大, 因为蛋糕摆成一个环所以 \(k_1=k_{m+1}\)\(n,m\le 2\times 10^5\).

题解

\(O(n^2\log n)\)

注意摆成一个环的话,\(\sum w_{k_j}\) 不会被顺序所影响,所以我们这时候只需要找到 \(\sum\limits_{j=1}^m|h_{k_j}-h_{k_{j+1}}|\) 的最小值.

会发现,一个环上一定会有最小和最大的 \(h_{k_j}\), 所以 \(\sum\limits_{j=1}^m|h_{k_j}-h_{k_{j+1}}|\) 的值一定是大于等于 \(2(\max h_{k_j}-\min h_{k_j})\)

也是可以取得到等的,所以就变成求 \(\sum\limits_{j=1}^mw_{k_j}-2(\max h_{k_j}-\min h_{k_j})\) 的最大值了.

\(h\) 排序之后,就是一个很好转移的式子.

\(\max\limits_{L=1}^n \max\limits_{R=L+m-1}^n\{ \max \sum\limits_{j=1}^mw_{k_j}-2(h_R-h_L)\}\)

先枚举 \(L\) 再枚举 \(R\) 然后拿个 set 什么的维护一下区间前 \(m\)

\(O(n\log^2 n)\)

这东西怎么优化呢,这又是一个小技巧哦

根据红日学长,这题具有决策单调性.

是的决策单调性并不一定是 dp 才会出现哒)φ(>ω<*)

我们通过打表找规律,或者做题经验,或者有做题经验的学长的锐评,可以得知对于每个 \(L\) , 取到最大值的 \(R\) 会满足:

\(R_1\le R_2\le \cdots \le R_n\)

于是我们考虑分治(?红日学长说是一个套路那我就记下来吧!)

怎么分治呢

我们要求 \(L=1, 2, 3, \cdots, n\) 的答案,利用上我们单调性这个性质

我们可以先暴力求出 \(L=mid\) 的时候的答案,也就是枚举 \(R\) ,记录下取到最大值的地方 \(R_{mid}\)

那么我们可以使用可持久化线段树(主席树)求出任意区间前 \(m\) 大之和,这样每一次找 \(ans_{mid}\)\(R_{mid}\) 就是 \(O(n\log n)\) 的.

可持久化线段树的学习笔记 还没补(!!) 补完了(`・ω・´)

然后分治求 \(L\in [1,mid-1]\)\(L\in [mid+1,R]\) 的答案,第二层转移来的 \(R\) 的范围根据单调性就是 \([1,R_{mid}]\)\([R_{mid},n]\)

因为分治最多就 \(\log n\) 层,所以时间复杂度是 \(O(n\log^2n)\)

分治每一次就记录一下 \(L\) 的范围和 \(R\) 的范围,具体看这段代码:

void work(int l, int r, int lb, int rb)
{
	if(l>r) return ; 
	int mid=(l+r)>>1;
	f[mid]=-inf; 
	for(int i=max(lb,mid+m-1); i<=rb; i++)
	{
		ll x=query_sumk(rt[i],rt[mid-1],1,M,m)-((p[i].h-p[mid].h)<<1); //就是那个式子
		if(x>f[mid]) f[mid]=x,R[mid]=i; //更新 ans[mid] 和 R[mid]
	}
	work(l,mid-1,lb,R[mid]),work(mid+1,r,R[mid],rb); // 递归下去找
}

整个的代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+5, M = 1e9+5;
const ll inf = 1e18;
int m,n,tot;
struct Misaka { int v,h; } p[N];
int R[N];
ll f[N];
struct Sagiri { int ls,rs,cnt; ll dat; } t[N<<5];
int rt[N];
void change(int &p, int q, ll l, ll r, int x)
{
	t[p=++tot]=t[q];
	if(l==r) { t[p].cnt++; t[p].dat+=l; return ; }
	ll mid=(l+r)>>1;
	if(x<=mid) change(t[p].ls,t[q].ls,l,mid,x);
	else change(t[p].rs,t[q].rs,mid+1,r,x);
	t[p].dat=t[t[p].ls].dat+t[t[p].rs].dat;
	t[p].cnt=t[t[p].ls].cnt+t[t[p].rs].cnt;
}
ll query_sumk(int p, int q, ll l, ll r, int k)
{
	if(l==r) return (ll)l*k;
	ll mid=(l+r)>>1;
	int rcnt=t[t[p].rs].cnt-t[t[q].rs].cnt;
	if(k<=rcnt) return query_sumk(t[p].rs,t[q].rs,mid+1,r,k);
	else return query_sumk(t[p].ls,t[q].ls,l,mid,k-rcnt)+t[t[p].rs].dat-t[t[q].rs].dat;
	
}
void work(int l, int r, int lb, int rb)
{
	if(l>r) return ;
	int mid=(l+r)>>1;
	f[mid]=-inf;
	for(int i=max(lb,mid+m-1); i<=rb; i++)
	{
		ll x=query_sumk(rt[i],rt[mid-1],1,M,m)-((p[i].h-p[mid].h)<<1);
		if(x>f[mid]) f[mid]=x,R[mid]=i;
	}
	work(l,mid-1,lb,R[mid]),work(mid+1,r,R[mid],rb);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%d%d",&p[i].v,&p[i].h);
	sort(p+1,p+1+n, [&] (const Misaka &x, const Misaka &y) { return x.h<y.h; });
	for(int i=1; i<=n; i++) change(rt[i],rt[i-1],1,M,p[i].v);
	work(1,n-m+1,m,n);
	ll res=-inf;
	for(int i=1; i<=n-m+1; i++) res=max(res,f[i]);
	printf("%lld\n",res);
	return 0;
}
posted @ 2022-11-13 16:33  copper_carbonate  阅读(20)  评论(0编辑  收藏  举报