DTOJ 2022.11.08 测试 题解

A 光

portal

题目大意

有四个格子,左上、右上、左下、右下分别可以填一个值 a,b,c,d,每个格子上的值 x 可以对自己贡献 x 的亮度,对相邻的两个格子贡献 x2 的亮度 ,对对角线的格子贡献 x4 的亮度 ,最后要满足每个格子的亮度分别大于等于 A,B,C,DA,B,C,D1500)求 a+b+c+d 的最小值

题解

1. O(n4)

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

2. O(n3)

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

3. O(n2logn)

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

4. O(1)

(准确来说是 O(244354) ,这么说其实跟上面那个差不多快)

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

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

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

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

但是我们注意到 a,b,c,d0 这个条件,所以我们枚举哪些是零,总共有 24 种情况.

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

#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 个函数 F1(x),F2(x),,Fn(x),每个函数为以下三种之一:

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

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

  1. 1 p w:表示令 Fp(x)=x+w
  2. 2 p w:表示令 Fp(x)=min(x,w)
  3. 3 p w:表示令 Fp(x)=max(x,w)
  4. 4 x:表示询问 (FnFn1F1)(x) 的值,其中 FG 表示函数 FG 的复合,即 (FG)(x)=F(G(x))

请维护以上操作。

对于所有测试数据,保证 1n1051q3×105opi{1,2,3}1pn1x108,保证任意时刻第 1 种函数中的 w[1,200] 之内,第 2,3 种函数中的 w[1,108] 之内。

题解

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

呃呃所以怎么写呢

方法1

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

f(x)=(1){y1,xx1x+b,x1<x<x2y2,xx2}

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

这是我的写法(我对于一个函数维护了它的最小值,最大值和中间那段线的纵截距 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+w1,w2},w3}

两个这种函数也具有可合并性,线段树上维护 w1,w2,w3 就好啦!

D 积木拼接

portal

题面大意

n 个蛋糕,每个蛋糕有 wi,hi 。选 m 个蛋糕满足 j=1mwkjj=1m|hkjhkj+1| 最大, 因为蛋糕摆成一个环所以 k1=km+1n,m2×105.

题解

O(n2logn)

注意摆成一个环的话,wkj 不会被顺序所影响,所以我们这时候只需要找到 j=1m|hkjhkj+1| 的最小值.

会发现,一个环上一定会有最小和最大的 hkj, 所以 j=1m|hkjhkj+1| 的值一定是大于等于 2(maxhkjminhkj)

也是可以取得到等的,所以就变成求 j=1mwkj2(maxhkjminhkj) 的最大值了.

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

maxL=1nmaxR=L+m1n{maxj=1mwkj2(hRhL)}

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

O(nlog2n)

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

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

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

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

R1R2Rn

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

怎么分治呢

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

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

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

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

然后分治求 L[1,mid1]L[mid+1,R] 的答案,第二层转移来的 R 的范围根据单调性就是 [1,Rmid][Rmid,n]

因为分治最多就 logn 层,所以时间复杂度是 O(nlog2n)

分治每一次就记录一下 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 @   copper_carbonate  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示